接口粒度

时间:2019-06-06 20:05:04

标签: design-patterns interface architecture

今天在我的工作中,我开始为接口的一部分而苦苦挣扎。

要在我的应用程序中维护一些主数据,我建立了一个上下文。在这种情况下,数据提供者可以获取所需的信息,并将其带入战略以持久化。

在第一次尝试中,我在上下文中构建了一个用于设置Dataprovider的方法。例如,一个基于文件,另一个来自数据库。

exports.meterSync = functions.database
  .ref('/{userId}/meter')
  .onUpdate((change, context) => {

    const meterReading = change.after.val();
    console.log(meterReading);

    // Get a reference to the Firestore document of the changed user
    const userDoc = admin.firestore().doc(`user/${context.params.userId}`);

    return userDoc.set(
      {
        meter: meterReading
      },
      { merge: true }
    );
  });

数据提供者需要一些不同的设置才能完成工作。基于文件的文件需要文件路径,数据库需要提供模型对象。

现在即时通讯不确定,定义接口的最佳方法是什么。 创建一个通用的接口“ Provider”,并注意getData方法的返回类型。或者继承,为Fileprovider和数据库建立一个特殊的接口。

Context->setData(MyProvider->getData() ) ;

第三种可能性是为每个提供程序创建一个独立的接口。

感谢您的攻关

1 个答案:

答案 0 :(得分:0)

我建议使用合成和基本的IProvider接口(您的第二种方法)。每个提供程序都可能具有一些单独的特殊功能,因此应该封装(接口隔离原理):

Main()
{
  Context context = new Context();

  IFileDataProvider fileDataProvider = new FileDataProvider();

  // Configure the provider
  fileDataProvider->setPath("c:\data");

  context->setProvider(fileDataProvider);

  // Get data. Internally this data is read from the local filesystem
  DataResultObject data = context->getData();

  IDatabaseProvider databaseProvider = new DatabaseProvider();

  // Configure the provider
  databaseProvider->login();

  context->setProvider(databaseProvider);

  // Get data. Internally this data is now read from the database
  DataResultObject data = context->getData();

  // A test case would fake the data provider to reduce complexity and improve performance. 
  // To achieve this a third implementation (a dummy) would be required.
  IProvider fakeDataProvider = new MockDataProvider();
  context->setProvider(fakeDataProvider);

  // Get test data. Internally this data is created by the fake data provider
  DataResultObject data = context->getData();
}

interface IProvider 
{
  DataResultObject getData();
}

interface IFileDataProvider extends IProvider
{
  void setPath(String path);
}

class FileDataProvider implements IFileDataProvider 
{
  void setPath(String path)
  {
    this->path = path;
  }

  DataResultObject getData()
  {
    return readFromFilesystem();
  }
}

interface IDatabaseProvider extends IProvider
{
  void login();
}

class DatabaseProvider implements IDatabaseProvider 
{
  private String credentials = "login credentials";

  void login()
  {
    login(this->credentials);
  }

  DataResultObject getData()
  {
    return readFromDatabase();
  }
}

class MockDataProvider implements IProvider
{
  DataResultObject getData()
  {
    // return empty or dummy data
    return new DataResultObject();
  }
}

class Context
{
  IProvider provider;

  public Context(IProvider provider)
  {
    this.provider = provider;
  }

  public DataResultObject getData()
  {
    return this.provider->getData();
  }

  public void setContext(IProvider provider) 
  {
    this.provider = provider;
  }
}

如果IProvider实例将在同一Context对象上更改,那么我将添加一个setter来切换实例。否则,构造函数是更好的选择。

为每种提供程序类型使用专用接口将消除在提供程序之间切换的选项,因为这将消除多态性。在编写单元测试时,切换可能会很有用。然后,您可以轻松模拟数据库或文件系统。

并且使用单个共享接口将强制例如DatabaseProvider来实现冗余的FileDataProvider特定方法。当类包含虚拟实现时,这可能会非常烦人和令人困惑。接口隔离原则(SOLID中的“ i”)出于一些很好的理由而建议避免这种设计。