4

I have this problem: Class1 and Class2 are of same Base type. They are representing the same concept but with slightly different implementation. If I model them "the right way" in Java, I would have it something like this:

abstract class Base{
    public int id;
    public abstract void someLogic();
}

class Class1 extends Base{
    List<Content> contents;

    @Override
    public void someLogic() {}
}

class Class2 extends Base{
    public Map<CategoryId, Category> categoryMap;

    @Override
    public void someLogic() {}
}

class CategoryId{
    int id;
}

class Category{
    CategoryId id;
    List<Content> contents;
}

class Content{
}

The problem with this design is that I have more classes to maintain than the following approach:

class Base{
    public int id;

    //WE KNOW IT IS Class1 IF CategoryId==null
    public Map<CategoryId, Category> categoryMap;

    public void someLogic(){}
}

class CategoryId{
    int id;
}

class Category{
    CategoryId id; //WILL ALWAYS BE NULL IF IN Class1 Map
    List<Content> contents;
}

class Content{
}

In later approach, I actually have CategoryId always null for Class1 case and for Class2 case it is actual instance of CategoryId. This is probably more unreadable and non-understandable but it keeps code cleaner.

Which design is better? Any other (alternative) implementation? Thanks.

  • In second approach, someLogic method contains if(CategoryId==null) statements to modify behavior, right? – Constantin Galbenu Mar 10 '17 at 16:22
  • Actually no. For Category==null there is no advanced logic. Only when !=null there will be some logic to handle different categories. If it was not odd I would model Class1 as Category - but in my domain it makes no sense. – Bojan Vukasovic Mar 10 '17 at 16:27
  • 1
    How is "more unreadable and non-understandable" cleaner? – Tulains Córdova Mar 13 '17 at 21:40
  • In a way that instead 100 classes you have 50 to handle. – Bojan Vukasovic Mar 13 '17 at 21:43
  • With more meaningful names (both for classes and for variables and methods ) it would be easier to discern which design is best, but with so little domain context one cannot say which is best. Is Class1 really a Base? Does it make sense for both Class1 and Class2 to extend Base? Dunno. – Tulains Córdova Mar 13 '17 at 21:55
1

It seems to make sense to go with implementation number 2 based off of the information given.

The question you have to ask yourself, though, is am I designing for change? Are there possible scenarios in the future whereby two discernible classes will be called for? If so, it would be better to design to accommodate this. Another way of asking it: is if you intend to add functionality or have the software grow later, will more logic be required to discern the two types? Will they have different functions?

The textbook example is base class is Animal and subclasses are different types of animals. The better design here is the first implementation, as different types of animals do vary quite a bit.

Where the second implementation makes sense is having a class for people, then assigning an id for gender - as opposed to subclassing male and female.

1

It appears that in your example that Class1 is the specific case where only one List<Content> object is needed or present. The Map<CategoryId, Category> implementation covers that case fine, so there's no need for a class hierarchy here. Option 2 is the simpler and more general design and therefore better, as long as all other things are equal.

I disagree with the notion that Option 1 is the "right way" in Java. Option 1 is like making a List class and ListOfOneItem class that inherits from List to handle that special case. Although for the example here it may not be that obvious at first to everyone (and it certainly wasn't for me). Sometimes we get confused between what should be two different classes and what should be two different instances of the same class because coming up with a good simple design is not as easy as it seems.

  • Isn't Option 1 more like having Integer and creating List<Integer> to handle case of multiple integers, and Option 2 actually the thing you described? – Bojan Vukasovic Mar 13 '17 at 15:32
  • I didn't see it that way because there's no common base class between Integer and List<Integer> that would make sense like option 1 has other than Object. Maybe that analogy is more fitting, but the names we have are Class1 and Class2 and so I don't really know what they're supposed to represent. – J. Lenthe Mar 13 '17 at 16:22
0

What you have is some logic that fits in neither class, and should be in its own class:

class Class1 {
    List<Content> contents;
    OtherLogic other;

    public Class1(OtherLogic other) {
        this.other = other;
    }

    public void someLogic() {
        other.someLogic();
    }
}

class Class2 {
    public Map<CategoryId, Category> categoryMap;
    private OtherLogic other;

    public Class2(OtherLogic other) {
        this.other = other;
    }

    public void someLogic() {
        other.someLogic();
    }
}

class OtherLogic {
    public void someLogic() {

    }
}

You have a dependency in both classes.


Or maybe neither class has any similarities, and what you want is a common interface.

  • That probably doesn't work. You have code duplication in class 1 and 2, and your OtherLogic class might need to know which of the concrete class is used to properly implement someLogic(), although I admit OP is unclear about this. – Arthur Havlicek Mar 13 '17 at 21:30
0

I'm not really sure of your real life example, but my understanding of your problem is that you have some logic that is independent of the way you are indexing your data, either in a list or a map. If I am correct then the obvious implementation would be something like this:

class CommonLogic{
    private Container myContainer;
    public void someLogic() {
        Content myContent = c.getContent();
        //...
    }

    public CommonLogic(Container c) {
        myContainer = c;
    }
}

interface Container {
     // Abstracts away all the data access/agregation logic
     public Content getContent();
}

class ListContainer implements Container {
    private List<Content> contents;
    //...
}

class ListContainer implements Container {
    public Map<CategoryId, Category> categoryMap;
    //...
}

I tend to think designing your classes with @override upfront is a design smell. My experience is that you only need to override when you are "patching" a design that is mostly finished, or using a class that mostly do what you want, and only need to rewrite a minor part, not when you are writing everything to fit a purpose from scratch.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.