1

Class Design Question

I am trying to figure out a good way to design my class system.

I have a class Product to compute various product specifications. I have two product model groups, each containing several model numbers: {10, 20, 30} and {50, 60, 70}. They share some similar features (i.e. the way weight is computed) but have different product add-on computations (i.e. some features depend on product length and some do not, depending on the model group)

The class model I currently have is main Product for all models (actually originally for 50, 60, 70 only), and a specific model Product10 was added on later and works for model 10 only. Now, a new addition brought on models 20, and 30, which are similar to model 10. Instead of adding them as separate classes, nearly identical to 10, I thought it was time to redesign.

Problem

In my current implementation, class Product10 is feeding off the main Product class (responsible for "most models") and Product10 is being the odd one out. Maybe it is fine, but now I am getting more model numbers similar to Product10 and I am seeking a better way to structure my code.

Currently I see that there are quite a few ways to design this.

  • create a class for each model (may be wasteful)
  • create a "god class" that contains code for all models (class may be bloated)
  • create subgroups for models, i.e. one class that covers a set of models like {10, 20, 30} or {50, 60, 70}
  • something else.

What I seek

I seek a good way to use OO design for my specific situation... Namely,

  • I think it is a good idea to have a generic class of some sort
  • I am not sure whether I should create a class for to represent "a set of similar models" or whether to treat models separately

My goal is to create an appropriate class based on model number.

What could be a good class decomposition?

  • "God Class" is a candidate for a software design pattern. I have seen other developers use it. I call it the "Root Class". – umlcat Nov 13 '15 at 23:41
2

If all your classes share the same logic, make a base class for it that contains the methods. All logic that is similar across all Products goes in this base class. For every group of projects that have similar logic for certain methods (e.g. calculating a feature that is the same across models), create a subclass that implements this logic.

Naturally, this only works if the calculation of features is not structured in such a way that it needs to be duplicated 'across'. For example, you can have two features 'Beta' and 'Gamma' that need to be calculated for three types of models A, B and C. If A and B share some calculation logic for 'Beta' you could create a base class for A and B that implements this logic. However, if your calculation for feature 'Gamma' is the same for A and C but not B this won't work.

In that case, I would recommend that you use the Strategy Pattern. Implement feature calculations in separate calculator classes and simply compose your product models by calling the right calculation classes. All models would then simply extend the single base class which defines all 'feature' calculations so the contract across your domain objects is the same.

0

Random Thoughts

The class model I currently have is main Product for all models (actually originally for 50, 60, 70 only) ... but have different product add-on computations

Design for the base case first. From there analyze carefully to identify precise differences with the one-off entities. Don't be so presumptive to tear things apart at the class level.

Now think how to conceptually abstract what's going on so as to see things that "are the same, but different." This is distinct from things that are just different.


have different product add-on computations (i.e. some features depend on product length and some do not, depending on the model group)

Template Pattern?

This could be a virtual method in a base class. Especially if there is some basic process and this method is a different implementation of the same step.


class Product10 is feeding off the main Product class (responsible for "most models") and Product10 is being the odd one out.

static things are a great way to keep functionality in a base class without it being part of instance implimentation; without inheriting it where not needed.


Think constantly about single responsibility principle. Even to the point of the possibility of functionality being given to existing classes - even framework classes - via extension methods.


create a "god class" that contains code for all models (class may be bloated)

Visitor Pattern?

Specialized code/class(es) targeted to the Product variations. This is "do stuff" code - perhaps it even "plugs into" a template (pattern) in a base class.


OO Principles are fractal.

  • Principles apply at class, method, flow control structure, etc.
  • Don't quit designing useful classes just because:

    • It would be "too small"
    • I'm 2 (3, 4, ...) levels deep in my inheritance or composite structure.
    • It's easy to write the code right now, right here.
  • Classes are data structures too.

    • I find that when I design in discrete, focused pieces the act of designing is forgiving.
    • I had a class that was 3 properties: Days, Months, Years. Only 1 method - adding this STRUCTURE to DateTime objects.
  • Method parameters IS dependency injection.

  • delegate methods is composition
0

If classes from the set {10, 20, 30} have the same behaviour, create only 1 class that derives from Product. Same for {50, 60, 70}.

abstract class Product {
    public function getWeight() {
        //Some computation
    }

    abstract public function getVolume();
}

class Product102030 extends Product {
    public function getVolume() {
        //Compute volume based on something
    }
}

class Product506070 extends Product {
    public function getVolume() {
        //Compute volume based on something else
    }
}

My goal is to create an appropriate class based on model number.

If I understand correctly, you also want a class able to instanciate the correct ProductXXXXXX based on a model number. This could typically be done into a factory.

class ProductFactory {
    public static function create($number) {
        switch($number) {
            case 10:
            case 20:
            case 30:
                return new Product102030;
            case 50:
            case 60:
            case 70:
                return new Product506070;
            default:
                throw new Exception ('Unsupported model number !');
        }
    }
}

A typical usage would be:

$p1 = ProductFactory::create(10); //$p1 is a Product102030 instance
$p2 = ProductFactory::create(60); //$p2 is a Product506070 instance
  • thanks. I am also currently looking into Strategy Pattern for a thing like Volume() in your example. Namely do not subclass, but inject a strategy where different strategy computes Volume differently, and inject it in Factory at creation time. It seems though that strategy may complicate the code a little bit. .... – Dennis Nov 13 '15 at 16:12
  • 1
    @Dennis Using a strategy there seems overcomplicated since not every product has its own behaviour. – Spotted Nov 14 '15 at 20:10
0

I think it is a good idea to have a generic class of some sort I am not sure whether I should create a class for to represent "a set of similar models" or whether to treat models separately My goal is to create an appropriate class based on model number. What could be a good class decomposition?

Quick Short Answer

"God Class" is a good idea, that is already used by others. Sometimes, "A class for represent a set of similar models" applies, sometimes "model number" applies.

Long Extended Boring Answer

Contrary to what many people do, one way to design a clas hierarchy, is not to have any hierarchy at all, and start with several independent classes.

Check which features does the classes share, as properties, what operations does, and how operations are done.

Another, thing to consider, is that some values, that are shared by several classes, maybe computed, and some not.

That is a way to detect fields from properties.

Figure 1, shows a U.M.L. class diagram, with 3 non related classes.

..............................
..+------------------------+..
..|  None: Model10Class    |..
..+------------------------+..
..| [+] string: ModelName; |..
..| [+] float:  Weight;    |..
..| [+] FSize:  Size;      |..
..| [+] ColorType: Color;  |..
..| [+] ...                |..
..+------------------------+..
..| [+] void:   Function1; |..
..| [+] ...                |..
..+------------------------+..
..............................
..+------------------------+..
..|  None: Model20Class    |..
..+------------------------+..
..| [+] string: ModelName; |..
..| [+] float:  Weight;    |..
..| [+] FSize:  Size;      |..
..| [+] ColorType: Color;  |..
..| [+] ...                |..
..+------------------------+..
..| [+] void:   Function1; |..
..| [+] void:   Function2; |..
..| [+] ...                |..
..+------------------------+..
..............................
..+------------------------+..
..|  None: Model30Class    |..
..+------------------------+..
..| [+] string: ModelName; |..
..| [+] float:  Weight;    |..
..| [+] FSize:  Size;      |..
..| [+] ColorType: Color;  |..
..| [+] ...                |..
..+------------------------+..
..| [+] void:   Function1; |..
..| [+] void:   Function2; |..
..| [+] void:   Function3; |..
..| [+] ...                |..
..+------------------------+..
..............................

Since, "ModelName", "Weight", "Size", and "Function1" are shared by all classes, lets generate a new base class with those members, and, subclass the previous classes from it.

The "..." means maybe there are, or not, other members, shared or not by other classes.

Figure 2, shows a U.M.L. class diagram, where a base class was extracted from the 3 classes.

..+------------------------+..
..|   None: ProductClass   |..
..+------------------------+..
..| [+] string: ModelName; |..
..| [+] float:  Weight;    |..
..| [+] FSize:  Size;      |..
..| [+] ColorType: Color;  |..
..+------------------------+..
..| [+] void:   Function1; |..
..+------------------------+..
..............................
..+------------------------+..
..|  None: Model10Class    |..
..+------------------------+..
..| [+] ...                |..
..+------------------------+..
..| [+] ...                |..
..+------------------------+..
..............................
..+------------------------+..
..|  None: Model20Class    |..
..+------------------------+..
..| [+] ...                |..
..+------------------------+..
..| [+] ...                |..
..| [+] void:   Function2; |..
..+------------------------+..
..................................
..+----------------------------+..
..| Model20Class: Model30Class |..
..+----------------------------+..
..| [+] ...                    |..
..+----------------------------+..
..| [+] ...                    |..
..| [+] void:   Function3;     |..
..+----------------------------+..
..................................

Note, that "Model30" also has the same members than "Model20".

[under construction]

...

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.