将服务逻辑与数据分离

时间:2012-07-27 06:48:16

标签: java design-patterns service

我一直在查看我在android项目中的几个类,我意识到我一直在混合逻辑和数据。在意识到这对我的项目的可读性和测试能力有多糟糕之后,我决定进行一些重构,以便将所有服务逻辑抽象出来以分离服务模块。但是,由于我一直依赖Java的多态性,我迷路了,需要一些指导。

假设我有一个超级数据类的“待更改”布局和两个子类:

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相关技术。

5 个答案:

答案 0 :(得分:5)

Visitor pattern救援。创建一个访问者界面,让每个服务实现此界面:

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类即可添加新服务。

答案 1 :(得分: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);
      ...
   }
}

您的元数据可以与保存或渲染分离,因此两者都可以相同。

答案 2 :(得分:0)

我会清除rendersaveToDB方法的“数据”类。

相反,我会为DataItem创建一个包装器层次结构(它不必完全模仿DataItem层次结构)。这些包装器将是实现这些方法的包装器。

此外,我建议(如果可以的话),转移到一些ORM(对象关系映射),如Hibernate或JPA,以摆脱saveToDB方法。

答案 3 :(得分:0)

首先,DataItem类应该是干净的,只有getter和setter,根本没有逻辑,就像 POJO 一样。而且 - 您的DataItem可能应该是抽象的。

现在 - 对于逻辑,像其他人建议我会为saveToDB部分使用一些ORM框架,但是你说它并没有帮助你导致它的android项目,你也有其他类似的方法。

所以我要做的是创建一个interface-IDataItemDAO,具有以下逻辑:

public interface IDataItemDAO<T extends DataItem > {
    public void saveToDB(T data, /* Some Arguments */);
    ... other methods that you need ...
}

我会为DataItem创建一个抽象的DAO,并将所有类似的代码放在所有DataItems中:

public abstract class ChildDataItemADAO impelemets IDataItemDAO<DataItem> {
    @Override
    public void saveToDB(DataItem data, /* Some Arguments */); {
        ...
    }
}

比我为你拥有的每个DataItem类创建一个DAO:

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);
}

答案 4 :(得分:0)

如果您想从数据中分离逻辑,可以尝试以下方法

创建数据类DataItem,ChildDataItemA,ChildDataItemB,而不使用对数据进行操作的方法

为您的数据类创建一个接口,例如

public interface OperationGroup1OnDataItem {
        public void saveToDB(DataItem dataItem/*plus other params*/) {

        }
        public void render(DataItem dataItem/*plus other params*/) {

        }
        ......
}

创建用于实施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);
}

您可以选择使用简单的静态映射实现工厂,其中您将类(或类fullName)作为键,将实现接口的Object作为值;

之类的东西
 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());

这是将逻辑与数据分离的一个示例,如果您希望从数据中分离逻辑,则必须从数据类中提取逻辑方法;否则没有分离。所以我认为每个解决方案都必须从删除逻辑方法开始。