1

I am fairly new to OO design and have problems with the design of some software and looking for a pattern or a combination of patterns that could help me solving my problem.

I have a type that has a collection of different geometric shapes (say lines, rectangles, and cubes). I actually even have composites of these shapes. Each shape has one or more values. A value is a coordinate (between one-dimensional for lines and three-dimensional for cubes).

All types of shapes have two methods that are essential. A method for adding values and a method for getting all values of a shape. E.g. Cube.AddValue(new Value3D{X=4,Y=7,Z=11}) or Line.AddValue(new Value1D{X=42}) respectively Cube.GetValues() would return a collection of Value3D and Line.GetValues() would return a collection of Value1D.

Since these methods have fundamentally different signatures, I can't just derive from a single interface to store all of these different objects in one collection. Splitting the collection into multiple separate collections, one for each type would be possible, but not desirable since they belong together (domain wise).

I've considered using three-dimensional values for all shapes and just ignoring Y/Z for lines/rectangles, but that isn't really nice and would actually violate the O and L in SOLID (a 4th dimension is possibly needed in the future).

Another possibility would probably be to use a generic Interface like IShape<T> where T is Value*D. This interface would look like:

public interface IShape<T> {
    public IEnumerable<T> GetValues();
    public void AddValue(T value);
}

But this would require another, non-generic interface INonGenericShape {} because I can't use a generic interface without specifying the type. I would end up with something like this:

public class SomeShape{
    private IList<INonGenericShape> _shapes = new List<INonGenericShape>();
}

But then I could as well use System.Object, since IShape does not provide any valuable information. So here I am, not knowing how to solve this (storing the objects in a single collection for consumers of that class). Can anyone provide some idea or insight?

  • What operations do you want to perform on this mixed collection of shapes? Usually if one is doing three-dimensional modeling, all objects are in a three-dimensional space, regardless of whether the objects have three-dimensional extent. – kevin cline May 6 '14 at 15:15
  • What causes you to need INonGenericShape? That is, are you actually storing a list where there are mixed dimensional shapes? Maybe it's just because this is an abstract or low-detail example, but if there's nothing non-generic in common between these shapes, you wouldn't gain anything by storing some of each type in a single list. Alternatively, maybe making the SomeShape class generic (so that there could be a 1, 2, or 3 dimensional SomeShape) is the solution. – TheRubberDuck May 6 '14 at 15:21
3

Contrary to many (perhaps most) object-oriented design tutorials out there, inheritance hierarchies should not necessarily match up with a domain's real-world taxonomy. In other words, just because all your objects are shapes in the real world doesn't necessarily mean they should all inherit from IShape in your program. Even if they do all share an interface, just because they have similar methods doesn't mean those methods need to be part of that interface.

The trick is to look at the calling code to see what operations you really need at that point. For example:

for shape in shapes:
  shape.doSomething()

Are you really going to call AddValue in a loop like that? I doubt it. More likely, it's going to be something like Draw or GetEditControl, and AddValue will be called before it's even put into shapes, or by a dimension-aware EditControl. In other words, IShape should only have methods that don't care about dimension, and operations that care about dimension should be handled by other interfaces.

3

Your shapes shouldn't hold their values through the interface. The interfaces are things you need to be able to do with them. The classes are the various ways you can fulfill those same things.

If your effort is to draw these shapes to the screen your interface is going to have nothing to do with values of any kind directly.

public interface IDrawable {
    void Draw(Graphics g);
}

This way you can invent completely new shape ideas you never thought of without having to change the signatures of how they interact. That is all the OO is for. Isolate the interaction to a single thing that will work for everything. (hint: You will often be abstracting a function, not a value.)

Now when you decide you need to be able to write these shapes to a data storage you can add.

public interface IWritable {
    void Write(Stream s);
}

Now there is place for these values to be typed, everything works better if its typed. Notice you can pretty much put anything here now, and freely mix-and-match.

public class Line : IDrawable , IWritable {
    public Point Start { get; set; }
    public Point End { get; set; }
    public void Draw(Graphics g) { }
    public void Write(Stream s) { }
}

public class ThreePointTriangle : IDrawable , IWritable {
    public Point First { get; set; }
    public Point Second { get; set; }
    public Point Third { get; set; }
    public void Draw(Graphics g) { }
    public void Write(Stream s) { }
}

public class SinRightTriangle : IDrawable , IWritable {
    public Point AngleA { get; set; }
    public Line Opposite { get; set; }
    public Line Hypotenuse { get; set; } 
    public void Draw(Graphics g) { }
    public void Write(Stream s) { }
}

public class Car : IDrawable {
    public void Draw(Graphics g) { }
}
1

The answer is simply, these aren't similar objects. Inheritance is about shared behaviour, not shared values. Just because these objects are made up of points doesn't mean they are related in terms of behaviour.

Leaving aside that getters and setters aren't really behaviour and should be avoided if at all possible*, even in this case the getters and setters are doing different things, as hinted by the different signatures. This should make it clear, these objects do not share behaviour beyond the most meaningless sense of storing points.

(this is a good example of why to avoid getters and setters, as they can cloud the true behaviour of an object, and make it appear that unrelated objects are related because they all have getter and setter methods)

0

How about something like this:

public interface ICoordinateValue {}

public class Value1D : ICoordinateValue {}
public class Value2D : ICoordinateValue {}
public class Value3D : ICoordinateValue {}


public interface IShape
{
    IEnumerable<ICoordinateValue> GetValues();
    void AddValue(ICoordinateValue value);
}
  • 1
    That's not good because it will allow adding Value1D to shapes which want a Value3D. – kevin cline May 6 '14 at 15:09
  • Yes, but that could be handled in AddValue. I know that this is not a perfect solution but if the need is to be very generic, I would say this is the least bad solution. – Håkan Fahlstedt May 6 '14 at 15:20

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.