In the book "Agile Software Development: Principles, Patterns, and Practices", the "Single Responsibility Principle" section has an example about a class Rectangle which has more than one responsibility:
class Rectangle {
public void draw() { ... }
public double area() { ... }
}
And different classes may use only one of its methods:
class ComputationalGeometryApplication {
Rectangle rectangle;
public void someMethod() {
double area = rectangle.area();
}
}
class GraphicalApplication {
Rectangle rectangle;
public void someMethod() {
rectangle.draw();
}
}
(Note: The book takes c++ for example, and I use Java code here since I'm not familiar with c++)
I'm not clear about this sentence:
Second, if a change to the GraphicalApplication causes the Rectangle to change for some reason, that change may force us to rebuild, retest, and redeploy the ComputationalGeometryApplication.
Say if GraphicalApplication causes the draw() method to become draw(boolean), why will it force us to rebuild, retest, and redeploy the ComputationalGeometryApplication?
I mean,
ComputationalGeometryApplication can choose to use the old version of compiled file of Rectangle if we know the changes causes by GraphicalApplication is not related with itself
If ComputationalGeometryApplication wants to use the new version of Rectangle, yes, we should rebuild/retest/redeploy. But even if the draw() method is not in Rectangle, but in another class of same codebase, we still need to rebuild/retest/redeploy the ComputationalGeometryApplication if we want to use the new version of the library which contains Rectangle
I can't really understand the benefit, what am I missing?
Update: The referenced question did help me a lot, since there is a slight difference with the two questions, I want to add my understanding here.
The key is this sentence in the accepted answer of that question:
"If the GraphicalApplication requires a new method or change in semantics in the Rectangle class, then that affects the ComputationalGeometryApplication since they both "link" to the Rectangle library"
The keyword link reminds me the Rectangle is in a shared library. When it's changed by GraphicalApplication, the ComputationalGeometryApplication will automatically uses the new version of the Rectangle class. If the change to the Rectangle is not compatible with previous one, say, it adds some references of extra class in the constructor of Rectangle:
class Rectangle {
Rectangle() {
SomeExtraClass cls = new SomeExtraClass()
}
}
For ComputationalGeometryApplication, it will fail if we didn't rebuild it since it may not find the class SomeExtraClass when initialise the Rectangle class
Related
I have read from other sources that it isn't a good idea for objects to know about each other especially the ones on same level. It should be more like hierarchy.
My problem is quite unique as I haven't figured out a way around it. Also I have been unlucky to come across any topic that addresses my issue specifically.
The problem
I am building a chess app and I am constructing the model of the app. Right now I have an abstract objects like for example Piece which other pieces like Queen, Knight and the rest would inherit from.
I also have a Board class which handles all board model and state of a game. Now each of my piece has a generateMove() method to calculate possible moves from their position and to do this they need to know the state of the board. Also the Pieces are been instantiated by Board at startup.
Question
Do I go ahead and instantiate Pieces by e.g
public class ChessBoard{
Boardbit = 64bit
Knight = new Knight(start_position, this)
//calculate
}
and then in Knight class method
public long generateMove(ChessBoard);
If no, what other ways can I go about it?
Making Chessboard to know the Knight and vise versa is not elegant. I agree with you in this point. Kind of sticking with the 'Tell don't ask' rule forces the 'higher level' element, in this case the chessboard, to tell the piece to move while providing all the required information. The Chessboard itself doesn't know which piece is moving (in this case of predicting possible moves for all pieces), but for sure never knows any details about how a piece can move or is allowed to move. This is just one possible solution using sort of Strategy Pattern. (The Visitor or some similar pattern could also be used here):
Main() {
chessboard = new Chessboard()
PiecesCollection = new PiecesCollection(new Knight(KnightStrategy, Color.Black))
chessboard.AddPieces(PiecesCollection)
CollectionOfAllPossibleMoveCollections = chessBoard.CallculateAllPossibleMoves()
Move selectedMove = ShowOrSelectMove(CollectionOfAllPossibleMoveCollections)
chessboard.ExecuteMove(selectedMove)
}
public class Chessboard{
// fields
PiecesCollectionWhite
// where 'PiecesCollectionWhite' is a collection of `Piece`
PiecesCollectionBlack
// where 'PiecesCollectionBlack' is a collection of `Piece`
CurrentlyVisitedPositionsCollection
// where 'CurrentlyVisitedPositionsCollection' is a collection of `Position`
// methods
AddPieces(PiecesCollection, Color)
CallculateAllPossibleMoves(Color) {
CollectionOfPossibleMoveCollections =
FOREACH Piece IN PiecesCollection OF Color
DO Piece.CalculateMoves(this.CurrentlyVisitedPositionsCollection)
return CollectionOfAllPossibleMoveCollections // where 'CollectionOfAllPossibleMoveCollections ' is a collection that holds a collection of `Move` of which each nested collection represents the possible moves of a chess piece.
}
ExecuteMove(Move) {
RemovePieceFromBoardIfNecessary(Move.ToPosition)
}
}
public class Piece
{
// fields
Strategy
Position
Color
// methods
CallculateMoves(CurrentlyVisitedPositionsCollection) {
PossibleMovesCollection = this.Strategy.Execute(CurrentlyVisitedPositionsCollection, this.Position)
return PossibleMovesCollection where `PossibleMovesCollection` is a collection of `Move`
}
}
public class Knight extends Piece
{
ctor(Strategy, Color)
}
public class Stragtegy
{
abstract Execute(currentPosition, allPiecesPositions) : PossibleMovesCollection
}
public class KnightStrategy extends Strategy
{
Execute(currentPosition, allPiecesPositions) {
PossibleMovesCollection = ApplyKnightMoveAlgorithm()
return PossibleMovesCollection
}
private ApplyKnightMoveAlgorithm() : PossibleMovesCollection
}
public class Move
{
Position fromPosition
Position toPosition
}
public class Color
{
Black
White
}
public class Position
{
Color
xCoordinate
yCoordinate
}
This is just a sketch and not a complete example. There is some state information or operations missing. E.g. maybe you would have to store the Color that was moved last on the chessboard.
Since the Chessboard returns all possible moves (information about all currently visited coordinates) you can easily enhance the algorithm by implementing some intelligence to predict the best possible moves from this information. So before the controller or in this case the Main() will call Chessboard.ExecuteMove(Move) it could make a call to PredictionEngine.PredictBestMove(CollectionOfAllPossibleMoveCollections).
Much better is to have method generateMove(boardState), so your Board should call whatever piece you have and pass them the necessary information for such task. It can be used even for some optimization as board can pregenerate some good-to-use structure each round only once and then pass it to all the pieces (like some 2d array).
The Open/Closed Principle states that software entities (classes, modules, etc.) should be open for extension, but closed for modification. I learned about this today and my teacher said that this concept is intrinsically connected to the concept of polymorphism. I can´t really see how both concepts are connected, can anyone explain please?
Here's my exaplanation.
Look at the following example:
public interface IShape
{
void Draw();
}
public class Square : IShape
{
public void Draw()
{
// DRAW SQUARE
}
}
public class Circle : IShape
{
public void Draw()
{
// DRAW CIRCLE
}
}
public class Renderer
{
public void DrawShapes(ICollection<IShape> shapes)
{
foreach (var shape in shapes)
{
shape.Draw();
}
}
}
This code is open to extensions and closed to modifications therefore it follows the OCP principle. Why? In case you need to make the application able to draw a new shape (e.g. Triangle), you don't need to modify the DrawShapes method of the Render class.
You only need to create a new class "Triangle" that implements the interface IShape and pass it to the DrawShapes method.
This code is also polymorphic because the "DrawShapes" method does not need to know the types of the shapes that it is rendering.
Pay attention to one thing: the closure of the O.C.P. principle is always strategic. What does it mean? It means that you cannot have code that is 100% closed to modifications. Example: what happens if you need to draw all the squares before the circles? In that case you have to modify the DrawShapes method; maybe with a Strategy pattern you can inject the policy to sort the drawing of the shapes.
From the perspective of object-oriented best practices, where should I place a variable or method needed in some children of a parent class, but not others?
Ex.
Classes Button, Knob, Lever, and Switch inherit from parent class Device.
Button, Lever, and Switch need a boolean isOn, but Knob does not. Where would you define isOn?
Lever and Switch need a method Throw() that toggles isOn; Button uses isOn but does not use Throw() to handle it. Does this affect your placement of isOn, and where would you define the Throw() method?
The above is purely an example; let's assume that there are distinct properties of each child class that distinguish it and that there are commonalities that make it reasonable to use the inheritence model discussed.
When only a sub-set of sub-classes share functionality, this can be expressed with an interface that contains the methods in question, which is only implemented by the sub-classes that need them.
public interface OnOffable {
boolean isOn();
void toggleOnOff();
void turnOn(boolean is_on);
void turnOn();
void turnOff();
}
class Switch extends Device implements OnOffable...
If one or more of the functions is moderately complicated, you can create a static utility function that helps prevent redundant code. In this example, however, the "complicated-ness" is the need to keep the on-off state.
In this situation, you can create an OnOffableComposer which (my preference) does not implement OnOffable.
And actually, since this particular interface can be completely implemented (meaning it needs no protected or abstract function), it can actually be a "simple" implementation of it:
public class SimpleOnOffable implements OnOffable {
private boolean isOn;
public class OnOffableComposer(boolean is_on) {
turnOn(is_on);
}
public boolean isOn() {
return isOn;
}
public void turnOn(boolean is_on) {
isOn = is_on;
}
public void toggleOnOff() {
turnOn(!isOn());
}
public void turnOn() {
turnOn(true);
}
public void turnOff() {
turnOn(false);
}
}
Here's how it's used:
public class Switch extends Device implements OnOffable {
private final SimpleOnOffable smplOnOff;
public Switch(boolean is_on) {
smplOnOff = new SimpleOnOffable(is_on);
}
public boolean isOn() {
return smplOnOff.isOn();
}
public void turnOn(boolean is_on) {
smplOnOff.turnOn(is_on);
}
public void toggleOnOff() {
smplOnOff.toggleOnOff();
}
public void turnOn() {
smplOnOff.turnOn();
}
public void turnOff() {
smplOnOff.turnOff();
}
}
Although the composer is "simple", this all demonstrates the concept of choosing composition over inheritance. It allows for much more complicated designs than single inheritance allows.
It sounds like the wrong abstraction all around. At the very least, Knob doesn't belong with the others. I might inject a class between Device and the three closely-related devices. Perhaps something like BinaryDevice:
abstract class Device {}
abstract class BinaryDevice : Device {
abstract void Activate();
abstract void Deactivate();
}
class Switch : BinaryDevice {
void Activate() { // activate the switch }
void Deactivate() { // deactivate the switch }
}
// same for Lever, which honestly is really just a Switch with a different styling and may not even need to be a separate object
class Button : BinaryDevice {
void Activate() { // activate the button, then immediately call Deactivate() }
void Deactivate() { // deactivate the button }
}
Knob can also inherit from Device, but at this point there is no common functionality for a Device so it's not clear why that universal common base class is even necessary. As further functionality is added to the various devices there may indeed be common functionality to push up to the base class. Indeed, there are well established refactoring patterns for dealing with generalization like this.
With the classes above, I imagine there would be error handling for trying to invoke an action in an incorrect state. For example, it's difficult to imagine a scenario where a Button would need anybody to call Deactivate() on it, since it de-activates itself. (Though just as a real-life button can become stuck, so too can this one if the action it invokes hangs for some reason.) But in any event even the Activate() and Deactivate() on the other objects would still need to check state. You can't activate a switch that's already active, for example.
The point is, the clarity of an object model starts to make itself more apparent when terminology and real-world modeling is more carefully considered. A lot of times developers try to shoehorn terminology into a handful of common terms in order to maximize their use of things like inheritance, and unfortunately this often results in the wrong abstraction and a confused domain model.
Build your objects as they naturally exist, then look for patterns which can be abstracted into common functionality. Don't try to define the common functionality first and then force objects to fit that mold.
In general, I would say that if an element of a parent class is needed in some but not all of the children then an intermediate parent should be introduced.
When defining an inheritance hierarchy, it's a logical assumption that the children of a parent should share all properties of that common ancestor. This is akin to the way a biological taxonomy would work, and it helps to keep the code clean.
So let's have a look at the objects in your system (we'll use the "is a" relationship to help us figure out the inheritance hierarchy):
Button, Knob, Lever, and Switch
Each of these might indeed be called "Devices", but when we say "devices" most people will probably think of digital devices like phones or tablets. A better word for describing these objects might be "controls" because these objects help you to control things.
Are all objects Controls? Indeed they are, so every object will have Control as a parent in its inheritance hierarchy.
Do we need to further classify? Well your requirements are to have an on/off status, but it does not make sense for every control to have on/off status, but only some of them. So let's further divide these into Control and ToggleControl.
Is every Control a ToggleControl? No, so these are separate classes of objects.
Is every ToggleControl a Control? Yes, so ToggleControl can inherit from Control.
Are all objects properly classified and are all parent attributes shared by all children? Yes, so we're done building our inheritance hierarchy.
Our inheritance hierarchy thus looks like this:
Control (Code shared by all controls)
/ \
/ \
Knob ToggleControl (Code shared by all controls that can also be toggled - Adds isOn)
\
\
Button, Lever, Switch
Now, to the other part of your question:
Lever and Switch need a method Throw() that toggles isOn; Button uses isOn but does not use Throw() to handle it. Does this affect your placement of isOn, and where would you define the Throw() method?
Firstly, "throw" is a reserved word (at least in Java), so using method names that are similar to reserved words might cause confusion. The method might be better named "toggle()".
Button should (in fact it must) use toggle() to toggle it's isOn since it is a togglable control.
I have a Layout Manager Class and this class designed for setting datagrid layout.
Code:
class LayoutManager
{
private object _target;
public LayoutManager(object aDataGrid)
{
_target = aDataGrid;
}
public void SaveLayout(string strProfileID)
{
}
public void LoadLayout(string strProfileID)
{
}
//in future I might add below function
public void ResetLayout()//OtherFunction0
{
}
public void OtherFunction1()
{
}
public void OtherFunction2()
{
}
}
According to OCP "a Class should be open for extension, but closed for modification". If I add the new function in LayoutManager Class, is this action violate the OCP? If yes, what is the proper way to design the class?
I don't think that adding methods to a class in general violates the OCP prinicple,
as this in fact extends the class's behviour.
The problem is if you change existing behaviours.
So that if the code on your added methods might change the behaviour of the existing methods
(because it changes the object's state) that would be a violation.
The correct way to follow the SOLID principals, is to make an interface:
ILayoutManager with the interfaces you want , with documented behaviours.
The class LayoutManager would implement this interface.
Other new methods might be added in a new interface, say ILayoutFoo or added to the existing interface, as long as they won't break the contract of the documented behaviour in the existing methods.
It's not possible to directly answer this without some concrete code.
Generally speaking though, the upshot of the OCP is that when classes derive from your base class and then override methods, the internal invariants shouldn't break because that's modification. There shouldn't be any way for the derived class to change those parts of the class' behaviour. The derived classes can change behaviour or add new functionality by using the parts exposed by the base class.
Whenever we speak about Open-Closed Principle, one important issue comes into play, which is called Strategic Closure.
It should be clear that no significant program can be 100% closed. In general, no matter how “closed” a module is, there will always be some kind of change against which it is not closed. Since closure cannot be complete, it must be strategic. That is, the designer must choose the kinds of changes against which to close his design. This takes a certain amount of prescience derived from experience. The experienced designer knows the users and the industry well enough to judge the probability of different kinds of changes. He then makes sure that the open-closed principle is invoked for the most probable changes.
For example in famous sample of Shape class you just grantee that your program (in side of Client and Shape)just is closed for modification about adding new shape.
public class Shape {
public draw() {
}
}
public class Circle extends Shape {
#Override
public void draw() {
// implementation special to Circle
}
}
public class Client {
...
public drawMyShape(Shape shape) {
shape.draw();
}
...
}
According to this Strategy, when you are designing your program, you should make a decision about the sections that you want to be closed to changes. Therefore, in your example, when you were designing your program, if you decided that your entity (in this case it is GraphCalculator class) should be closed for modification and open to extension regarding to adding new functionality, adding new function in this example violates Open-Closed Principle, due to the fact that it changes implementation in side of client and GraphCalculator class. And solution can be using abstraction, which is mentioned in previous answers.
I am looking for an alternative to the visitor pattern. Let me just focus on a couple of pertinent aspects of the pattern, while skipping over unimportant details. I'll use a Shape example (sorry!):
You have a hierarchy of objects that implement the IShape interface
You have a number of global operations that are to be performed on all objects in the hierarchy, e.g. Draw, WriteToXml etc...
It is tempting to dive straight in and add a Draw() and WriteToXml() method to the IShape interface. This is not necessarily a good thing - whenever you wish to add a new operation that is to be performed on all shapes, each IShape-derived class must be changed
Implementing a visitor for each operation i.e. a Draw visitor or a WirteToXml visitor encapsulates all the code for that operation in one class. Adding a new operation is then a matter of creating a new visitor class that performs the operation on all types of IShape
When you need to add a new IShape-derived class, you essentially have the same problem as you did in 3 - all visitor classes must be changed to add a method to handle the new IShape-derived type
Most places where you read about the visitor pattern state that point 5 is pretty much the main criteria for the pattern to work and I totally agree. If the number of IShape-derived classes is fixed, then this can be a quite elegant approach.
So, the problem is when a new IShape-derived class is added - each visitor implementation needs to add a new method to handle that class. This is, at best, unpleasant and, at worst, not possible and shows that this pattern is not really designed to cope with such changes.
So, the question is has anybody come across alterative approaches to handling this situation?
You might want to have a look at the Strategy pattern. This still gives you a separation of concerns while still being able to add new functionality without having to change each class in your hierarchy.
class AbstractShape
{
IXmlWriter _xmlWriter = null;
IShapeDrawer _shapeDrawer = null;
public AbstractShape(IXmlWriter xmlWriter,
IShapeDrawer drawer)
{
_xmlWriter = xmlWriter;
_shapeDrawer = drawer;
}
//...
public void WriteToXml(IStream stream)
{
_xmlWriter.Write(this, stream);
}
public void Draw()
{
_drawer.Draw(this);
}
// any operation could easily be injected and executed
// on this object at run-time
public void Execute(IGeneralStrategy generalOperation)
{
generalOperation.Execute(this);
}
}
More information is in this related discussion:
Should an object write itself out to a file, or should another object act on it to perform I/O?
There is the "Visitor Pattern With Default", in which you do the visitor pattern as normal but then define an abstract class that implements your IShapeVisitor class by delegating everything to an abstract method with the signature visitDefault(IShape).
Then, when you define a visitor, extend this abstract class instead of implementing the interface directly. You can override the visit* methods you know about at that time, and provide for a sensible default. However, if there really isn't any way to figure out sensible default behavior ahead of time, you should just implement the interface directly.
When you add a new IShape subclass, then, you fix the abstract class to delegate to its visitDefault method, and every visitor that specified a default behavior gets that behavior for the new IShape.
A variation on this if your IShape classes fall naturally into a hierarchy is to make the abstract class delegate through several different methods; for example, an DefaultAnimalVisitor might do:
public abstract class DefaultAnimalVisitor implements IAnimalVisitor {
// The concrete animal classes we have so far: Lion, Tiger, Bear, Snake
public void visitLion(Lion l) { visitFeline(l); }
public void visitTiger(Tiger t) { visitFeline(t); }
public void visitBear(Bear b) { visitMammal(b); }
public void visitSnake(Snake s) { visitDefault(s); }
// Up the class hierarchy
public void visitFeline(Feline f) { visitMammal(f); }
public void visitMammal(Mammal m) { visitDefault(m); }
public abstract void visitDefault(Animal a);
}
This lets you define visitors that specify their behavior at whatever level of specificity you wish.
Unfortunately, there is no way to avoid doing something to specify how visitors will behave with a new class - either you can set up a default ahead of time, or you can't. (See also the second panel of this cartoon )
I maintain a CAD/CAM software for metal cutting machine. So I have some experience with this issues.
When we first converted our software (it was first released in 1985!) to a object oriented designed I did just what you don't like. Objects and Interfaces had Draw, WriteToFile, etc. Discovering and reading about Design Patterns midway through the conversion helped a lot but there were still a lot of bad code smells.
Eventually I realized that none of these types of operations were really the concern of the object. But rather the various subsystems that needed to do the various operations. I handled this by using what is now called a Passive View Command object, and well defined Interface between the layers of software.
Our software is structured basically like this
The Forms implementing various Form
Interface. These forms are a thing shell passing events to the UI Layer.
UI layer that receives Events and manipulate forms through the Form interface.
The UI Layer will execute commands that all implement the Command interface
The UI Object have interfaces of their own that the command can interact with.
The Commands get the information they need, process it, manipulates the model and then report back to the UI Objects which then does anything needed with the forms.
Finally the models which contains the various objects of our system. Like Shape Programs, Cutting Paths, Cutting Table, and Metal Sheets.
So Drawing is handled in the UI Layer. We have different software for different machines. So while all of our software share the same model and reuse many of the same commands. They handle things like drawing very different. For a example a cutting table is draw different for a router machine versus a machine using a plasma torch despite them both being esstentially a giant X-Y flat table. This because like cars the two machines are built differently enough so that there is a visual difference to the customer.
As for shapes what we do is as follows
We have shape programs that produce cutting paths through the entered parameters. The cutting path knows which shape program produced. However a cutting path isn't a shape. It just the information needed to draw on the screen and to cut the shape. One reason for this design is that cutting paths can be created without a shape program when they are imported from a external app.
This design allows us to separate the design of the cutting path from the design of the shape which are not always the same thing. In your case likely all you need to package is the information needed to draw the shape.
Each shape program has a number of views implementing a IShapeView Interface. Through the IShapeView interface the shape program can tell the generic shape form we have how to setup itself up to show the parameters of that shape. The generic shape form implements a IShapeForm interface and registers itself with the ShapeScreen Object. The ShapeScreen Object registers itself with our application object. The shape views use whatever shapescreen that registers itself with the application.
The reason for the multiple views that we have customers that like to enter shapes in different ways. Our customer base is split in half between those who like to enter shape parameters in a table form and those who like to enter with a graphical representation of the shape in front of them. We also need to access the parameters at times through a minimal dialog rather than our full shape entry screen. Hence the multiple views.
Commands that manipulate shapes fall in one of two catagories. Either they manipulate the cutting path or they manipulate the shape parameters. To manipulate the shape parameters generally we either throw them back into the shape entry screen or show the minimal dialog. Recalculate the shape, and display it in the same location.
For the cutting path we bundled up each operation in a separate command object. For example we have command objects
ResizePath
RotatePath
MovePath
SplitPath
and so on.
When we need to add new functionality we add another command object, find a menu, keyboard short or toolbar button slot in the right UI screen and setup the UI object to ececute that command.
For example
CuttingTableScreen.KeyRoute.Add vbShift+vbKeyF1, New MirrorPath
or
CuttingTableScreen.Toolbar("Edit Path").AddButton Application.Icons("MirrorPath"),"Mirror Path", New MirrorPath
In both instances the Command object MirrorPath is being associated with a desired UI element. In the execute method of MirrorPath is all the code needed to mirror the path in a particular axis. Likely the command will have it's own dialog or use one of the UI elements to ask the user which axis to mirror. None of this is making a visitor, or adding a method to the path.
You will find that a lot can be handled through bundling actions into commands. However I caution that is not a black or white situation. You will still find that certain things work better as methods on the original object. In may experience I found that perhaps 80% of what I used to do in methods were able to be moved into the command. The last 20% just plain work better on the object.
Now some may not like this because it seems to violate encapsulations. From maintaining our software as a object oriented system for the last decade I have to say the MOST important long term thing you can do is clearly document the interactions between the different layers of your software and between the different objects.
Bundling actions into Command objects helps with this goal way better than a slavish devotion to the ideals of encapsulation. Everything that is needs to be done to Mirror a Path is bundled in the Mirror Path Command Object.
Visitor design pattern is a workaround, not a solution to the problem. Short answer would be pattern matching.
Regardless of what path you take, the implementation of alternate functionality that is currently provided by the Visitor pattern will have to 'know' something about the concrete implementation of the interface that it is working on. So there is no getting around the fact that you are going to have to write addition 'visitor' functionality for each additional implementation. That said what you are looking for is a more flexible and structured approach to creating this functionality.
You need to separate out the visitor functionality from the interface of the shape.
What I would propose is a creationist approach via an abstract factory to create replacement implementations for visitor functionality.
public interface IShape {
// .. common shape interfaces
}
//
// This is an interface of a factory product that performs 'work' on the shape.
//
public interface IShapeWorker {
void process(IShape shape);
}
//
// This is the abstract factory that caters for all implementations of
// shape.
//
public interface IShapeWorkerFactory {
IShapeWorker build(IShape shape);
...
}
//
// In order to assemble a correct worker we need to create
// and implementation of the factory that links the Class of
// shape to an IShapeWorker implementation.
// To do this we implement an abstract class that implements IShapeWorkerFactory
//
public AbsractWorkerFactory implements IShapeWorkerFactory {
protected Hashtable map_ = null;
protected AbstractWorkerFactory() {
map_ = new Hashtable();
CreateWorkerMappings();
}
protected void AddMapping(Class c, IShapeWorker worker) {
map_.put(c, worker);
}
//
// Implement this method to add IShape implementations to IShapeWorker
// implementations.
//
protected abstract void CreateWorkerMappings();
public IShapeWorker build(IShape shape) {
return (IShapeWorker)map_.get(shape.getClass())
}
}
//
// An implementation that draws circles on graphics
//
public GraphicsCircleWorker implements IShapeWorker {
Graphics graphics_ = null;
public GraphicsCircleWorker(Graphics g) {
graphics_ = g;
}
public void process(IShape s) {
Circle circle = (Circle)s;
if( circle != null) {
// do something with it.
graphics_.doSomething();
}
}
}
//
// To replace the previous graphics visitor you create
// a GraphicsWorkderFactory that implements AbstractShapeFactory
// Adding mappings for those implementations of IShape that you are interested in.
//
public class GraphicsWorkerFactory implements AbstractShapeFactory {
Graphics graphics_ = null;
public GraphicsWorkerFactory(Graphics g) {
graphics_ = g;
}
protected void CreateWorkerMappings() {
AddMapping(Circle.class, new GraphicCircleWorker(graphics_));
}
}
//
// Now in your code you could do the following.
//
IShapeWorkerFactory factory = SelectAppropriateFactory();
//
// for each IShape in the heirarchy
//
for(IShape shape : shapeTreeFlattened) {
IShapeWorker worker = factory.build(shape);
if(worker != null)
worker.process(shape);
}
It still means that you have to write concrete implementations to work on new versions of 'shape' but because it is completely separated from the interface of shape, you can retrofit this solution without breaking the original interface and software that interacts with it. It acts as a sort of scaffolding around the implementations of IShape.
If you're using Java: Yes, it's called instanceof. People are overly scared to use it. Compared to the visitor pattern, it's generally faster, more straightforward, and not plagued by point #5.
If you have n IShapes and m operations that behave differently for each shape, then you require n*m individual functions. Putting these all in the same class seems like a terrible idea to me, giving you some sort of God object. So they should be grouped either by IShape, by putting m functions, one for each operation, in the IShape interface, or grouped by operation (by using the visitor pattern), by putting n functions, one for each IShape in each operation/visitor class.
You either have to update multiple classes when you add a new IShape or when you add a new operation, there is no way around it.
If you are looking for each operation to implement a default IShape function, then that would solve your problem, as in Daniel Martin's answer: https://stackoverflow.com/a/986034/1969638, although I would probably use overloading:
interface IVisitor
{
void visit(IShape shape);
void visit(Rectangle shape);
void visit(Circle shape);
}
interface IShape
{
//...
void accept(IVisitor visitor);
}
I have actually solved this problem using the following pattern. I do not know if it has a name or not!
public interface IShape
{
}
public interface ICircleShape : IShape
{
}
public interface ILineShape : IShape
{
}
public interface IShapeDrawer
{
void Draw(IShape shape);
/// <summary>
/// Returns the type of the shape this drawer is able to draw!
/// </summary>
Type SourceType { get; }
}
public sealed class LineShapeDrawer : IShapeDrawer
{
public Type SourceType => typeof(ILineShape);
public void Draw(IShape drawing)
{
if (drawing is ILineShape)
{
// Code to draw the line
}
}
}
public sealed class CircleShapeDrawer : IShapeDrawer
{
public Type SourceType => typeof(ICircleShape);
public void Draw(IShape drawing)
{
if (drawing is ICircleShape)
{
// Code to draw the circle
}
}
}
public sealed class ShapeDrawingClient
{
private readonly IDictionary<Type, IShapeDrawer> m_shapeDrawers =
new Dictionary<Type, IShapeDrawer>();
public void Add(IShapeDrawer shapeDrawer)
{
m_shapeDrawers[shapeDrawer.SourceType] = shapeDrawer;
}
public void Draw(IShape shape)
{
Type[] interfaces = shape.GetType().GetInterfaces();
foreach (Type #interface in interfaces)
{
if (m_shapeDrawers.TryGetValue(#interface, out IShapeDrawer drawer))
{
drawer.Draw(drawing);
return;
}
}
}
}
Usage:
LineShapeDrawer lineShapeDrawer = new LineShapeDrawer();
CircleShapeDrawer circleShapeDrawer = new CircleShapeDrawer();
ShapeDrawingClient client = new ShapeDrawingClient ();
client.Add(lineShapeDrawer);
client.Add(circleShapeDrawer);
foreach (IShape shape in shapes)
{
client.Draw(shape);
}
Now if someone as the user of my library defines IRectangleShape and wants to draw it, they can simply define IRectangleShapeDrawer and add it to ShapeDrawingClient's list of drawers!