The goal is to create an application that has objects that can represent some operations (add, subtract, etc).

All of those objects will have common functions and members, and thus will either implement an interface or inherit from an abstract class (Which would be better practice, this will be in C# if that matters?).

As far as I can see, there are two different ways of organizing all of these classes.

  1. I could create an addition class, a subtraction class, etc. This has the upside of being highly modular but the difference between classes is so minimal.
  2. I could create one class, and have a member that will say what type of operation is being represented. This means lots of switch statements, and losing some modularity, in addition to being harder to maintain.

Which is is better practice? Is there a better way of doing that is not listed above? If it matters, the list of functions that should be supported is long.

  • 2
    As of right now the example is a bit vague as the type of input and result is important to know before you decide design pattern or (even simpler solution) you should use. Even if this is about mathmatical operations should they be handled with values such as int, long or BigInteger, or will it be mixed between the different type of objects themselves? – Spoike May 31 '12 at 5:56
  • Right now, everything starts out as an int or double, but I promote the int to a decimal if need be. This is more of an implementation detail that I am not too concerned with though (at least at this point). I may just make everything a decimal though. – soandos May 31 '12 at 6:06
  • "harder to maintain" is a very, very strong hint towards the answer... – Izkata May 31 '12 at 15:00
  • I've solved this type of problem before with an application of the Strategy Pattern (just look at the Java example) and Command Pattern. This was before I became aware of F# and other functional languages... so I basically wrote a small library that allowed me to stack operations and lazy-eval stuff. – Clockwork-Muse May 31 '12 at 17:34

In an object-oriented world dominated by languages like Java and C#, option 1 is the way to go. There are different ways you can model your data, for example another way is with a tree that can be walked. But sticking with the spirit of your question, there are two approaches to consider. In both cases it is important to use interfaces. Writing programs on top of abstractions is far more flexible than writing them on top of concrete implementations.

The first approach is to extract common code into abstract super classes. This is really easy to do with languages like Java and C#, and at first glance seems like the obvious choice. The main problem with this approach is that it is too easy to conflate inheritance with the abstractions you are trying to create. I argue that these abstract classes created entirely to share common, but otherwise unimportant to inheritance, code are part of of the implementation, and thus shouldn't be relied upon in client code. This may or may not be a problem, but in either case there is nothing that can be done to prevent it from happening.

To remedy this, the second approach would be to to favor composition over abstract classes. While this is not as easy to implement as the first, it does address the problem left by abstract classes. Namely, there can be no reliance in the artifacts left by the implementation details of the abstract classes, as long as you properly encapsulate the composed objects. If, however, the abstract classes (if they even need to be abstract) are important to the inheritance hierarchy, and are thus more than just "common code buckets", then favoring composition will reduce the usefulness of the classes.

In the end I think it depends on whether the abstract classes are important to building a proper abstraction based on inheritance, or if the common code is just there to ease the burden of maintaining it. It may be flat-out annoying to implement if there are a lot of methods that just delegate to an encapsulated object, but that trade-off might be worth it for something more pure. However, some languages such as Ruby make it easy to support composition with its modules, which are basically mixins.

  • With modern IDEs delegate methods can be created pretty much automatically. This should not stop anyone. +1 esp. for the discussion on when inheritance is relevant in the interface. – scarfridge May 31 '12 at 6:35

It depends on how much these classes have in common:

  • Nothing at all - make a bunch of standalone classes.
  • They have the same methods, but implement them all differently - make an interface and a bunch of classes that implement it.
  • They share some core functionality, but other functionality differs - make a base class and derive your classes from that.

Depending on the complexity of your classes, you may want to have more than one level in your class hierarchy if you go with the last option.

There's also a fourth option, which is especially viable if all your class needs to have is one operation: take a functional-programming approach and ditch the classes altogether. Instead, implement a function for each operation (in C#, this will have to be a static method somewhere, or you'll have to return lambdas from a factory method, as the language lacks free functions) and pass those around. Most likely, you want to declare a delegate type for this.

  • For that last one, basically it would be one class that has a Func<T> member (if I want to do this with lambdas) that literally holds the operation? – soandos May 31 '12 at 6:02
  • @soandos: more like a class with a static method that returns anonymous functions, or a class with some static methods that you use directly. – tdammers May 31 '12 at 6:04

.3. One base class, overriding implementations where needed in child classes.

Why? Main reason: Any changes required only happen in a single location and propagate to all dependents.

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.