首页 > 解决方案 > 界面粒度

问题描述

在我今天的工作中,我开始与我的界面部分斗争。

为了在我的应用程序中维护一些主数据,我构建了一个上下文。此上下文需要数据提供者获取所需的信息并将其引入持久化策略。

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

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

数据提供者需要一些不同的设置来完成这项工作。Filebased 需要 Filepath 和 databaseprovider 一个 Modelobject。

现在我不确定,定义接口的最佳方式是什么。制作一个通用接口“Provider”并关心 getData 方法的返回类型。或者继承为 Fileprovider 和数据库构建一个特殊的接口。

Interface Fileprovider extends IProvider { void setPath(String Path) ;} 

或者第三种可能性只是为每个提供者做一个独立的接口。

谢谢你的坚韧

标签: design-patternsinterfacearchitecture

解决方案


我会建议组合和基本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 来切换实例。否则构造函数是更好的选择。

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

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


推荐阅读