Как структурировать интерфейсы, когда объекты используют только часть интерфейса?

9

У меня есть проект, в котором у меня есть два класса, которые требуют объект доступа к базе данных, который обновляет одну и ту же таблицу. Ограничения фреймворка и проекта делают его таким, что я не могу объединить эти два класса. Я создал случай ниже, который показывает, как настройки. Класс A должен иметь возможность обновлять и читать запись, а класс B должен иметь возможность обновлять и удалять запись.

Если я использую классы такими, какие они есть, это работает просто отлично, но у меня возникает проблема с тем фактом, что каждому из классов требуется функциональность, которую он не использует для реализации. Например, чтобы использовать класс A, я должен передать ему dao, который реализует функцию удаления, даже если он никогда не будет вызван. Точно так же я должен передать класс B дао, который реализует функцию чтения, но он никогда не будет вызван.

Я думал о том, чтобы приблизиться к этому, имея интерфейсы, которые наследуют другие (IReadDao, IUpdateDao, IDeleteDao - это daos, которые будут унаследованы от), но этот подход в основном требовал бы различного интерфейса для каждой комбинации функций (IUpdateAndRead, IReadAndDelete, IReadAndUpdate ... )

Я хочу использовать интерфейс для дао, потому что я не хочу связывать приложение с базой данных. Есть ли образец или метод для достижения того, что я хочу, о котором кто-нибудь знает? Заранее спасибо.

class IDao {

  void update(ModelDao model);
  void delete(String guid);
  ModelDao read(String guid);

}

Class A {

  private IDao dao;

  public A(IDao dao) {

    this.dao = dao;

  }

  public void doStuff() {

    ModelDao model = new ModelDao();

    ...

    dao.update(model);

  }

  public void readThenDoSomething(String id) {

    ModelDao model = dao.read(id);

    ...

  }

}

Class B {

  private IDao dao;

  public B(IDao dao) {

    this.dao = dao;

  }

  public void makeUpdate() {

    ModelDao model = new ModelDao();

    ...

    dao.update(model);

  }

  public void delete(String id) {

    dao.delete(id);

  }

}
jteezy14
источник
2
Зачем вам нужны отдельные интерфейсы для каждой комбинации, а не просто чтобы каждый класс, использующий их, реализовывал те, которые им нужны?
Yitzih
В приведенном выше случае, вместо того, чтобы передавать IDao в конструктор A, я должен был бы передать объект, который реализует IUpdate и IRead, так какой будет тип переменной экземпляра "dao"? Разве это не должно быть что-то вроде IUpdateAndReadDao? Он по-прежнему должен быть интерфейсом, потому что, если я скажу ему взять реализацию, специфичную для базы данных, я подключу класс к БД. Это то, что вы спрашивали?
jteezy14
3
Я думаю, что это прекрасный пример принципа разделения интерфейса ( Iот SOLID). Возможно, захотите немного прочитать об этом.
Кристофер Франциско

Ответы:

10

Согласно комментарию Кристофера, лучше разделить интерфейсы . Так что вам нужно, по крайней мере IReadDao, IDeleteDaoи IUpdateDao. Обратите внимание, что вам не обязательно нужны три класса; у вас может быть один большой класс DAO, который реализует все три интерфейса, если имеет смысл комбинировать кодовую базу таким образом.

Для того, чтобы избежать комбинаторных взрыва (например , чтобы избежать необходимости применения IReadUpdate, IDeleteUpdateи т.д. интерфейса) вы можете предоставить интерфейсы отдельно в инъекции конструктора (вы можете пройти один и тот же объект в два раз разных параметров), или предоставить один объект , поддерживающих два или более интерфейсов в общем вызове метода с использованием extends.

Конструктор впрыска:

class MyDaoLibrary : IUpdateDao, IInsertDao, IDeleteDao {
    //Etc....
}

class A
{
    //It is OK if the IoC container factory provides the same instance for both parameters.
    a(IUpdateDao dao1, IDeleteDao dao2) {
        this.updater = dao1;
        this.deleter = dao2;
    }
    //Etc....
}

Инъекция сеттера, используя общий метод:

<T extends IUpdateDao & IDeleteDao> void InitializeDao(T dao)  //Pass a single object that implements both IUpdateDao and IDeleteDao
Джон Ву
источник
При использовании инжектора установки, как бы я объявил переменную экземпляра, которую я устанавливаю в функции InitializeDao?
jteezy14
Вам понадобятся две переменные экземпляра (одна для удалений, одна для обновлений) ... назначьте daoобеим.
Джон Ву
О да, это имеет смысл. Большое спасибо за отличный ответ!
jteezy14