15

我一直在查看我在一个 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()andrender()方法移至服务类。但是,有时我需要能够在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();
    }
}

我仍然在每个子类中保留“虚拟”方法来调用适当的服务方法。但是,我认为这并没有真正实现我想要的分离,因为数据类仍然会知道服务(糟糕!)。

关于如何解决这个问题的任何想法?

编辑:请注意,这render()只是saveToDB()这些方法的通用示例,因此问题不在于选择 ORM 或 SQL 相关技术。

4

5 回答 5

5

访客模式来救援。创建一个访问者接口并让每个服务实现这个接口:

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类。

于 2012-08-04T04:44:09.767 回答
2

所以你想做:

List<DataItem> dataList; 
for (DataItem item: dataList) {
    service.saveToDB(item);
    service.render(item);
}

为此,您需要为您的服务设置一个系统,以便从您的 DataItem 子类中了解更多详细信息。

ORM 和序列化程序通常通过元数据系统解决这个问题,例如通过查找名称与子类匹配的 xml 文件,其中包含要保存或序列化的属性。

ChildDataItemA.xml
<metaData>
   <column name="..." property="..."/>
</metaData>

您可以通过反射和注释获得相同的结果。

在您的情况下,桥模式的应用程序也可以工作:

class DataItem {
    public describeTo(MetaData metaData){
       ...
    }    
}

class Service {
   public void saveToDB(DataItem item) {
      MetaData metaData = new MetaData();
      item.describeTo(metaData);
      ...
   }
}

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

于 2012-07-29T09:15:40.237 回答
0

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

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

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

于 2012-07-27T06:58:11.200 回答
0

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

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

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

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

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

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);
}
于 2012-07-29T09:47:23.123 回答
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)作为键,将实现接口的对象作为值;就像是

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

这是从数据中分离逻辑的一个例子,如果你想从数据中分离逻辑,你的逻辑方法必须从你的数据类中提取出来;否则就没有分离。所以我认为每个解决方案都必须从删除逻辑方法开始。

于 2012-08-04T16:04:16.580 回答