The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

Objects/needs that are very similar but different?

When you have objects that are very similar, but their behavior is slightly different, what's the normal way to deal with this?  Or vice versa, the behavior is the exact same, but the knowledge required to get said behavior changes (for example, reading in CSV files from different sources).

These are the general solutions that I can think of.  Anyone with any others and/or insights please post :)

- Polymorphic
  * All variations inherit from a parent and override/overload the relevant methods.
  * Can be done via compile time polymorphism, aka templates

- Separation of interface/implementation, generally used with a factory method.
  * Different objects that completely replicate functionality but inherit from a base interface, said factory method will be used to return the actual object used.

- Super Object.  It knows all, just configure it.


There are two things that have brought about this question.  The first is that as I get more experienced I'm moving further and further away from "OOP" programming.  I'm starting to think that inheritance in general has led  us down the wrong path.

The second is because I recently came across code that solved this very problem (with the CSV that I mentioned earlier).  Their solution was to use inheritance/polymorphism, which made it *very* hard to find any discernable algorithm (parts of it were here, parts up the hierarchy, etc.).


How would you solve this problem specifically, and in general?
Somewhat Experienced
Friday, June 15, 2007
 
 
Strategy pattern.
Mike Stockdale Send private email
Friday, June 15, 2007
 
 
Yep, strategy looks like the way to go... aggregation always beats inheritence (e.g. the Template pattern) in the long run (unless of course its cool stuff like CRTP).
Tz Send private email
Friday, June 15, 2007
 
 
Break things down into interface, behavior and implementation.

Here are some ground rules:
1) Describe your domain hierarchy at the interface level (a hierarchy of interfaces). Interfaces should be correctly segregated.
2) Describe behavior in abstract classes. They should depend only on interfaces and other RELATED abstract classes (with the same closure)
3) Implementations are derived in most cases from abstract behavior.
4) The only things which are exported to the client are interfaces, utility classes and class factories. Some abstract behavior classes may be exportable too if you want to allow the client to extend behavior via inheritance (this is typically framework).
5) Implementations are not visible to client classes.

// A hierarchy of interfaces.
interface Service { void go(); }
interface LogService extends Service {...}
interface SecurityService extends Service {...}

// Common to all LogServices
class abstract AbstractLogService implements LogService {
  private final Properties properties;
  private final Logger internalLogger;
 
  public LogService(Propertie properties) {
      this.properties = properties;
  }

  // Connects a client to the Log service.
  public static Logger connect() {...}

  public void go() {
    this.internalLogger = getIternalLogger(properties);
    start();
  }
 
  // Cannot be specified because we don't know the type of logger
  protected abstract Logger getIternalLogger(Properties properties);
}

// Common behavior to all security services.
class abstract AbstractSecurityService implements SecurityService {
  private SecurityManager mgr;

  public boolean isAuthentic(User user) {...}
  public boolean canDo(User user, Action action) {...}
}


// Log4J specifics
class Log4JService extends AbstractLogService { ... }
// JavaLog specifics
class JavaLogService extendsLogService { ... }
// JAAS Specific
class JaasService extends AbstractSecurityService {...}

A number of DP can help (depending on the context): bridge, visitor, decorator, strategy, class factory, etc.

For example instead of exporting abstract behavior we can use the bridge pattern to offer extensibility points (Open Close class design principle OCP).

// Before
// Exported to public
public class SomeFacotry {
    static SomeInterface create() {return new SomeImplementation();
}
public interface SomeInterface {void go();}
public class abstract SomeBehavior implements Some {
    public void go() {
      doStuff();
    }
    protected abstract void doStuff();
}
// private
class SomeImplementation extends SomeBehavior {
  protected void doStuff() {...}
}


In this case clients can either create the SomeImplementation or extend by inheriting from SomeBehavior. Encapsulation is somewhat broken.


// After
// Exported to public
public class SomeFactory {
    public static SomeInterface create(SomeExtension ext) {...}
}
public interface SomeInterface {void go();}
public interface SomeExtension { void doStuff();}
// private
class abstract SomeBehavior implements Some {
  private final SomeExtension extension;
  public SomeBehavior(SomeExtendsion extension) {
      this.extension = extension;
  }

  public void go() {
      extension.doStuff();
  }
}

In this case the code is completely closed to modification, but can be extended by client classes via the SomeExtension interface (OCP principle again). Encapsulation is not broken.
Dino Send private email
Friday, June 15, 2007
 
 
> Their solution was to use inheritance/polymorphism, which made it *very* hard to find any discernable algorithm

The algorithm should have been crystal clear because it should have worked in terms of methods the were implemented by a derived class.

OOP isn't about inheritance, people over use it. But for an algorithm inheritance works perfectly. It would be interesting to what was done to cloud the implementation.
son of parnas
Friday, June 15, 2007
 
 
How would I solve this problem?

a) Have one class (A) that implements the bits that all objects share. The bits that aren't shared - make some smaller classes that all implement an interface (B). Use the interface (B) in (A) to call in the varying bits as required.

That works in the case that all classes are KNOWN to share a single common set of stuff. If this isn't the case....

b) Have a single interface (A) that underlies all the implementations.

Create a class (B) that contains the common stuff you've identified. Use composition to make (B) a member of each of your concrete implementations ((C),(D) etc) of interface (A). This approach allows you to add the shared code into as many (or as few) of the implementations as you like.

c) Templates can work quite nicely too - although you should probably try and separate any exactly identical stuff out into concrete classes.
Duncan Sharpe
Friday, June 15, 2007
 
 
If I understand your requirements correctly, I suggest the Strategy pattern too. In case you're not familiar with it:

You have a class that handles all of the behavior that is always the same. That class contains, as a member field, an object that encapsulates the varying behavior. This is either an abstract base class or an interface, so you plug in various concrete subclasses to get the desired specific behavior. You probably instantiate the concrete class and pass it into the constructor of the main class, or perhaps pass in a type code and have your main class create the correct subclass (Factory Method pattern), etc.
Jesse Send private email
Friday, June 15, 2007
 
 
"OOP isn't about inheritance, people over use it."

son of parnas, why do you say that? I consider polymorphism to be pretty much the most important and powerful concept of OOP.

(Of course, you can do polymorphism without inheritance, by using interfaces, but more often than not I find myself with an abstract base class that holds common functionality.)
Jesse Send private email
Friday, June 15, 2007
 
 
> why do you say that?

I say that because people associate animal-dog type hierarchies with OOP when OOP isn't about modeling the real world at all. It's about structuring software. Sometimes inheritance is a good way to do that. Sometime. Other times it's HASA, it's creating rules, etc.
son of parnas
Friday, June 15, 2007
 
 
Nitpick: Templates are generics or a fancy preprocessor, but decidedly not object polymorphism. (At the very least, they are closer to aggregation than polymorphism which implies inheritance.)

As for polymorphism being overrated - well, without it OOP is merely modular programming, somewhat useful but nothing revolutionary. To really get all the advantages of OOP, you need to have some common interface plus varying implementation in it. Which usually is polymorphism, or sometimes aggregation. The art of OOP is knowing when to use which, and that involves recognizing objects which seem monolithic but are really composite.
ping?
Saturday, June 16, 2007
 
 
<< Nitpick: Templates are generics or a fancy preprocessor, but decidedly not object polymorphism.

I'm not sure what you're nitpicking as I specifically stated compile time polymorphism.  Object polymorphism (which I do not believe is a technical term) is a completely different creature.



Thanks for the responses guys.  It's late and I'm tired so I haven't really attempted to grok any of this.  I'll take a closer look at it tomorrow :)
Somewhat Experienced
Saturday, June 16, 2007
 
 
Consider these three related design patters:

1 State: "Allow an object to alter its behavior when its internal state changes." Its basic idea is to encapsulate the behavior of the object when it is in a certain state, as a separate object and delagate the execition of the methods (this is pattern is commonly applied when implementing communication protocols or device drivers, because the behavior depends a lot of the current state)

2.Strategy: "define a family of algorithms, encapsulate each one, and make them interchangeable"

3. Algoritm template: "Define the skeleton of an algorithm in an operation, deferring some steps to subclasses."
Pablo Chacin Send private email
Saturday, June 16, 2007
 
 
I am nitpicking at the proposition that templates are related to polymorphism at all. They are related to macros and generics, and somewhat to interface/implementation separation. Their similarity to polymorphism is an illusion: they produce variants on an interface, not implementations of the same interface.
ping?
Saturday, June 16, 2007
 
 
Tz Send private email
Saturday, June 16, 2007
 
 
Looked. The article is an example of why templates and  polymorphism aren't the same: the article explores uses of templates combined with polymorphism, and you cannot speak of polymorphism combined with polymorphism without uttering a tautology.

Interesting application of generics to achieve something like aspect programming, though. I'd say it is misguided, but an interesting trick nonetheless.
ping?
Monday, June 18, 2007
 
 
I think you've chosen to wage war against what you deem to be a bastardization of the word polymorphism and nothing that anyone shows/links/explains to you will change your mind in that regard, but that isn't going to stop me from responding :)

What you're referring to  is known as subtyping polymorphism.  C++ employs this type of polymorphism due to the tight coupling between a type and its interface, but C++ is a multi-paradigm language, and thus, there are several types of polymorphism that can be applied.  One of these is parameterized polymorphism (what I referred to as compile time polymorphism).  One has only to look into the std algorithms to see an example of this in use.
Somewhat Experienced
Thursday, June 21, 2007
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz