Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Separating Service Logic from Data

I have been looking over a couple of classes I have in an android project, and I realized that I have been mixing logic with data. Having realized how bad this can be to the readability and the test-ability of my project, I decided to do some refactoring in order to abstract away all services logic to separate services modules. However, since I have been relying on Java's polymorphism, I got lost and need some guidance.

Suppose I have this "to-be-changed" layout for a super data class, and two sub-classes:

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 */
    }
}

Now, I thought about moving the saveToDB() and render() methods to a service class. However, sometimes I need to be able to call these method into instance of compiled type DataItem without knowing its runtime type. For instance, I might want to make the following call:

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

Additionally, I thought of doing the following:

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

Where I still keep 'dummy' methods in each subclass that would call an appropriate service method. However, I do not think that this really achieves the separation I want since data classes will still know about services (bad!).

Any ideas on how to solve this?

Edit: Note that render() and saveToDB() are just generic examples of what these methods can be, so the problem is not really about choosing an ORM or SQL related techniques.

like image 335
Sam Avatar asked Jul 27 '12 06:07

Sam


People also ask

How do you separate business logic?

Separating the UI from Business Logic The concept is simple: divide a computer program into sections that each address a separate and distinct concern of the application. This so-called "concern" can be something as simple as naming a class that is instantiated or as broad as platform specific details (Figure 1).

Why do we separate business logic?

By separating your models from views, you reduce the number of dependencies between them. Therefore, changes made to one of the layers have the lowest possible impact on other layers. This separation also improves the code reusability.


2 Answers

Visitor pattern to the rescue. Create a visitor interface and have each service implement this interface:

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 { ... }

Then have each DataItem implement an accept method:

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

Note that all accept implementations look the same but this refers to the correct type in each subclass. Now you can add new services without having to change the DataItem classes.

like image 142
casablanca Avatar answered Oct 01 '22 12:10

casablanca


So you want to do:

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

For this you need to setup a system for your service to know more details from your DataItem subclass.

ORM's and serializers usually solve this via a metadata system, e.g. by finding an xml file with name matching the subclass, containing the properties to save or serialize.

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

You could get the same result via reflection and annotations.

In your case, an application of the Bridge pattern could also work:

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

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

Your metadata could be decoupled from saving or rendering, so you can the same for both.

like image 37
GeertPt Avatar answered Oct 01 '22 13:10

GeertPt