Разделение логики службы из данных
Я просматривал несколько классов, которые у меня есть в проекте Android, и я понял, что я смешиваю логику с данными. Поняв, насколько плохо это может быть с точки зрения удобочитаемости и тестовой способности моего проекта, я решил сделать некоторые рефакторинг, чтобы абстрагировать логику всех служб на отдельные модули услуг. Однако, поскольку я полагался на полиморфизм Java, я потерялся и нуждаюсь в некоторых рекомендациях.
Предположим, что у меня есть этот "to-be-changed" макет для суперкласса данных и два подкласса:
public class DataItem {
/* some variables */
public saveToDB(/* Some Arguments */) {
/* do some stuff */
}
public render() {
/* render the class */
}
}
public class ChildDataItemA extends DataItem {
@Override
public saveToDB(/* Some Arguments */) {
super.saveToDB();
/* more specific logic to ChildDataItemA */
}
@Override
public render() {
/* render logic for ChildDataItemA */
}
}
public class ChildDataItemB extends DataItem {
@Override
public saveToDB(/* Some Arguments */) {
super.saveToDB();
/* more specific logic to ChildDataItemB */
}
@Override
public render() {
/* render logic for ChildDataItemB */
}
}
Теперь я подумал о переносе методов saveToDB()
и render()
в класс службы. Однако иногда мне нужно иметь возможность вызвать этот метод в экземпляр скомпилированного типа DataItem
, не зная его тип времени выполнения. Например, я могу сделать следующий вызов:
List<DataItem> dataList;
for (DataItem item: dataList) {
item.saveToDB();
item.render();
}
Кроме того, я подумал о следующем:
public class ChildDataItemB extends DataItem {
@Override
public saveToDB(/* Some Arguments */) {
super.saveToDB();
/* more specific logic to ChildDataItemB */
Service.saveToDBB();
}
@Override
public render() {
/* render logic for ChildDataItemB */
Service.renderB();
}
}
Где я все еще сохраняю методы 'dummy' в каждом подклассе, который вызовет соответствующий метод службы. Тем не менее, я не думаю, что это действительно достигает разделения, которое я хочу, поскольку классы данных все равно будут знать об услугах (плохо!).
Любые идеи о том, как это решить?
Изменить: обратите внимание, что render()
и saveToDB()
являются просто типичными примерами того, что эти методы могут быть, поэтому проблема заключается не в выборе методов ORM или SQL.
Ответы
Ответ 1
шаблон посетителя для спасения. Создайте интерфейс посетителя и каждый сервис реализует этот интерфейс:
public interface DataItemVisitor {
// one method for each subtype you want to handle
void process(ChildDataItemA item);
void process(ChildDataItemB item);
}
public class PersistenceService implements DataItemVisitor { ... }
public class RenderService implements DataItemVisitor { ... }
Затем каждый DataItem
реализует метод accept
:
public abstract class DataItem {
public abstract void accept(DataItemVisitor visitor);
}
public class ChildDataItemA extends DataItem {
@Override
public void accept(DataItemVisitor visitor) {
visitor.process(this);
}
}
public class ChildDataItemB extends DataItem {
@Override
public void accept(DataItemVisitor visitor) {
visitor.process(this);
}
}
Обратите внимание, что все реализации accept
выглядят одинаково, но this
относится к правильному типу в каждом подклассе. Теперь вы можете добавлять новые службы без изменения классов DataItem
.
Ответ 2
Итак, вы хотите:
List<DataItem> dataList;
for (DataItem item: dataList) {
service.saveToDB(item);
service.render(item);
}
Для этого вам нужно настроить систему для своей службы, чтобы узнать больше сведений из вашего подкласса DataItem.
ORM и сериализаторы обычно решают это с помощью системы метаданных, например. путем поиска файла xml с именем, соответствующим подклассу, содержащим свойства для сохранения или сериализации.
ChildDataItemA.xml
<metaData>
<column name="..." property="..."/>
</metaData>
Вы можете получить тот же результат через отражение и аннотации.
В вашем случае также может работать приложение Bridge pattern:
class DataItem {
public describeTo(MetaData metaData){
...
}
}
class Service {
public void saveToDB(DataItem item) {
MetaData metaData = new MetaData();
item.describeTo(metaData);
...
}
}
Ваши метаданные могут быть отделены от сохранения или рендеринга, поэтому вы можете сделать то же самое для обоих.
Ответ 3
Я бы очистил классы "данных" методов render
и saveToDB
.
Вместо этого я создам иерархию оболочек для DataItem (ей не нужно точно имитировать иерархию DataItem). Этими обертками будут те, которые реализуют эти методы.
Кроме того, я предлагаю (если можно), вы переходите к некоторому ORM (объектно-реляционное сопоставление), например Hibernate или JPA, чтобы избавиться от метода saveToDB.
Ответ 4
Прежде всего, класс DataItem должен быть чистым, только с геттерами и установщиком и без какой-либо логики, как POJO. Кроме того, ваш DataItem может быть абстрактным.
Теперь, поскольку логика, как и другие, предположила, что я бы использовал некоторую структуру ORM для части saveToDB, но вы сказали, что это не помогает вам вызывать проект android, и у вас есть другие методы, подобные этому.
Итак, что бы я сделал, это создать интерфейс IDataItemDAO со следующей логикой:
public interface IDataItemDAO<T extends DataItem > {
public void saveToDB(T data, /* Some Arguments */);
... other methods that you need ...
}
Я бы создал абстрактную DAO для DataItem и поместил бы ее весь похожий код всех DataItems:
public abstract class ChildDataItemADAO impelemets IDataItemDAO<DataItem> {
@Override
public void saveToDB(DataItem data, /* Some Arguments */); {
...
}
}
чем я бы создал DAO для каждого класса DataItem, который у вас есть:
public class ChildDataItemADAO extends DataItemDAO impelemets IDataItemDAO<ChildDataItemA> {
@Override
public void saveToDB(ChildDataItemA data, /* Some Arguments */); {
super(data, ...);
//other specific saving
}
}
другая часть - это то, как использовать правильный DAO для правильного экземпляра, для этого я бы создал класс, который принесет мне правильный DAO для данного экземпляра, это очень простой метод, если инструкции if-else ( или вы можете сделать это динамически с помощью карты класса и DAO)
public DataItemDAO getDao(DataItem item) {
if (item instanceof ChildDataItemA) {
//save the instance ofcourse
return new ChildDataItemADAO();
}
}
поэтому вы должны использовать его следующим образом:
List<DataItem> dataList;
for (DataItem item: dataList) {
factory.getDao(item).saveToDB(item);
}
Ответ 5
Если вам нужна отдельная логика из данных, вы можете попробовать следующий подход
Создайте свой класс данных DataItem, ChildDataItemA, ChildDataItemB без метода, работающего с данными
Создайте интерфейс для некоторых операций над вашим классом данных, например
public interface OperationGroup1OnDataItem {
public void saveToDB(DataItem dataItem/*plus other params*/) {
}
public void render(DataItem dataItem/*plus other params*/) {
}
......
}
Создайте factory для реализации оператора OperationGroup
public class OperationFactoryProvider {
public static OperationGroup1OnDataItem getOperationGroup1For(Class class) {
....
}
}
Используйте его в своем коде:
List<DataItem> dataList;
for (DataItem item: dataList) {
OperationGroup1OnDataItem provider OperationFactoryProvider.getOperationGroup1For(item.class);
provider.saveToDB(item);
provider.render(item);
}
Вы можете реализовать factory с помощью простой статической карты, где вы кладете класс (или класс fullName) в качестве ключа и объект, реализующий интерфейс в качестве значения; что-то вроде
Map<String,OperationGroup1OnDataItem> factoryMap= new HashMap<String,OperationGroup1OnDataItem>();
factoryMap.put(DataItem.class.getName(),new SomeClassThatImplementsOperationGroup1OnDataItemForDataItem());
factoryMap.put(ChildDataItemA.class.getName(),new SomeClassThatImplementsOperationGroup1OnDataItemForChildDataItemA());
Реализация getOperationGroup1For:
return factoryMap.get(item.getClass().getName());
Это один из примеров разделения логики на данные, если вам нужна отдельная логика из данных, ваши логические методы должны быть извлечены из вашего класса данных; в противном случае нет разделения. Поэтому я думаю, что каждое решение должно начинаться с устранения логических методов.