What is the strategy pattern with reversed flow of control? - oop

In my understanding the strategy pattern is used to make behaviour interchangable. This involves that the responsibility of the strategy is defined in an interface, to which the client may then delegate calls. E.g. suppose a value can be obtained in different ways, the interface would have a method "getValue()".
My question concerns the case where the flow of control is opposite. For example if the concrete strategy initiates the request "onValueChanged()" on the client (suppose it has a reference to the client or a callback interface).
Is this still considered a strategy pattern?
Update - added the following source code example:
interface DataSupplierCb
{
void onValueChanged(int a);
}
interface DataSupplier
{
void check();
}
// NOTE 1: Data supplier knows how the get the value
class ConcreteDataSupplier : public DataSupplier
{
void check()
{
myDataSupplierCb.onValueChanged(47);
}
}
class Client : public DataSupplierCb
{
void onValueChanged(int a)
{
// NOTE 2: Client knows what to do with the value
}
void changeDataSupplier(int i)
{
if (i == 1)
{
myCurrentDataSupplier = new ConcreteDataSupplier(this);
}
}
}

No. That would not be the strategy pattern. In the strategy pattern, the strategy interface, and the concrete strategy implementations do not know about the client.
The client knows about the strategy interface, and knows nothing about the actual implementations.
The goal of this pattern is the ability of replacing one strategy with another without modifying the client. A strategy is usually some sort of algorithm.
What you are describing seems to be closer to the Observer design pattern in which there is a subject and one or several observers implementing a common interface (or inheriting from a common base class). The subject is the object that is being observerved, and the observers are objects that need to be notified whenever the subject changes. e.g: the subject can be some kind of data source, and one observer can be an histogram view, and another a pie chart view.
http://en.wikipedia.org/wiki/Observer_pattern
http://en.wikipedia.org/wiki/Strategy_pattern

If the intent of the DataSupplier interface to allow your Client to swap in, and delegate to, different concrete data-fetching implementations then yes it can be considered a strategy. Your Client is shielded from the details (aka strategy) used to fetch the value as expected in the use of the Strategy pattern. And the fact that the Client reference is passed to the Strategy is fine and common:
(From the GoF)
"Strategy and Context interact to implement the chosen algorithm. A
context may pass all data required by the algorithm to the strategy
when the algorithm is called. Alternatively, the context can pass
itself as an argument to Strategy operations. That lets the strategy
call back on the context as required."
The Context for you is Client.
Now that all being said, rare is a solution that uses only one pattern. Your notification does seem to use the Observer pattern as another poster commented, and that is fine.
What I don't like about what you have implemented though is that your Strategy is a pure interface. Not always a bad thing, but in this case, with that notification callback, an interface does not provide a guarantee that the notifictaion callback is going to happen. Interfaces only guarantee the method signatures. I would recommend using the Template pattern in a base class to derrive the strategies from.
abstract class DataSupplier
{
protected ClientInterface _client;
// ctor takes in context
public DataSupplier(ClientInterface client)
{
_client - client;
}
public void check()
{
int priorValue = 46;
int newValue = OnGetValue();
if (priorValue != newValue)
_client.onValueChanged(newValue)
}
protected abstract int OnCheck();
}
And then:
class ConcreteDataSupplier : DataSupplier
{
// Check, and notification, are handled by the base. We only need
// to implement the actually data fetching
int OnGetValue()
{
return someValue;
}
}
With this approach, I know the notification will be handled. I don't need to worry about an implementor forgetting it in a new strategy later.

Related

Implementing multiple overloaded functions vs doing inside checking/validation for enforcing business logic

This is a general issue, but i will demonstrate it using the following problem:
I want to model airCrafts. now, for every aircraft there is one capability that is loaded with: attackCapability, IntelligenceCapability and BdaCapability. Not all aircraft can be loaded with all capabilities - every airCraft has its own potential capabilities that is supports and can be loaded with.
I want to implement this business logic with composition:
every aircraft object will hold a private member of type Capability (every capability will be implemented by a class that inherits from this abstract class/interface).
Now - I want to design a code that will enforce the business logic - i.e. will not allow any programmer to load an aircraft with an ability it doesn't support.
I have two options for doing this:
implement several overloaded version of the SetCapability() function - each one with the type of capability i want to support. For example:
public void SetCapability(AttackCapability capability);
public void SetCapability(BdaCapability capability);
That way the user can't load the aircraft with forbidden capability - and if he/she will try doing so, there will be a compilation erorr - i.e. that IDE will show some error message.
implement one function: public void SetCapability(Capability capability)
and doing some checking inside it. something like this:
public void SetCapability(Capability capability){
if(capability.getType() != typeOf(AttackCapability) || capability.getType() != typeOf(BdaCapability){
throw new InvalidOperationException();
}
_capability = capability;
}
the disdvantage here is that misuse of the user will be identified only at runtime instead at compiletime (much harder to identify and more bug prone), but as opposed to the previous option - it doesn't make you write several overloaded function which may cause the class to look heavy, strange and less readable for the inexperienced programmer.
(code reusability is not a big issues, because we always can implement private function like: private void SetCapabiltiy(Capability capability); which do the setting logic and every public overloaded SetCapability function will call it).
I feel that both option have their advantages and disadvantages as i described above.
I tend to prefer the first one, because it is more safe and hermeric - but it frequently causes my code to look "ugly" and some programmers may interprete it as duplicated code and don't understand the point...
I would like to hear your opinion for this issues, and maybe you have some better designs for this problem.
I couldn't understand your #1 option, but I think in any option you said you need to check permitted capabilities statically. This would result to change the code in future and would break the OCP. Instead of this I thought that maybe we can take advantage of dynamic dispatch here and let it to check types.
IMHO you can create final concrete classes and set required access modifiers to private in the concretes and then use factories(actually the abstract factory pattern looks suitable here) to hide object creation from clients to enforce business logic and the use the technique(which is my term) that referencing the same type used in Decorator or Chain of Responsibility patterns to keep capabilities in a chain by which you may have extra possibilities to dynamically check the capabilities to apply the behavior they require instead of just in a list( which could be used with Command pattern also)
As a note, the section where you mention as your second option limits the extensibility and generally manual type check is considered as bad practice in a dynamically dispatched or duck typed OOP language runtime. I know the fact that you are checking the field not a type but this is also a manual control and capability as the name implies is a behavior not state.
Finally since the aircrafts don't have same functionalities, but varying behaviors Visitor pattern could be used to design functionalities by which you create method classes instead of classes containing methods.
In addition, a Map<Aircraft, List<Capability>> could be used by keeping in a config object chek the features when creating objects by using DI.
//Capability types
interface Capable {
do();
}
class Attacker implements Capable {
private Capable capability;
public Attacker(Capable capability) { //to keep track of next/linked capability
this.capability = capability;
}
public do(String request) {
this.attack();
this.capability.do();//or instead of this decorator you could use chain of responsibility with next() and handle() below.
}
//to select features in some functionality.
public handle(String request) {
if ("attack".equals(request)) { //just to show what you can by keeping a reference to same type
this.attack();
} else {
this.capability.do();
}
}
public next(Capable capability) {
this.capability = capability;
}
}
class Intelligent implements Capable {
//similar to above.
}
//Aircraft types
class F111 implements Aircraft {
private Capable capability;
//or store capabilities in a list and check with config mapper object(maps aircrafts with its capabilities in a Map<Aircraft.TYPE, List<Capable> capabilities)
//private List<Capable> capabilities;
//other state
Aircraft(Capable capability) { //or create a factory
this.capability = capability;
}
//behaviors
doSth() {
this.capability.do();
}
}
class F222 implements Aircraft {
//...
}
//To hide creation of requested aircraft with only its required capabilities from the client
static class AircraftFactory { //dont permit to directly access to Aircraft concretes
static Aircraft getAircraft(String type) {//could be used abstract factory instead of this.
if("f111".equals(type)) {
return new F111(new Attacker(new Intelligent()));
}
else if(...) { new F222(new Intelligent(new Bda())}
else if(...) { new F001(new Default()) }
}
}
class Client {
main() {
//instead of this
//Aircraft f9999 = new Aircraft(new Attacker);
//f9999.doSth();
//enforce client to use factory.
Aircraft aircraft = AircraftFactory.getAircraft("f222");
aircraft.doSth();
}
}

Benefits of an abstract class with a factory constructor?

I've recently run into some examples that make use of abstract classes as interfaces but also add factory constructors to the abstract interface so it can in a sense be "newed" up. For example:
abstract class WidgetService {
factory WidgetService() = ConcreteWidgetService;
Widget getWidget();
void saveWidget(Widget widget);
}
class ConcreteWidgetService extends BaseWidgetService implements WidgetService {
WidgetService();
Widget getWidget() {
// code to get widget here
}
void saveWidget(Widget widget) {
// code to save widget here
}
}
Usages of this service would be in some other service or component like so:
WidgetService _service = new WidgetService();
Based on my understanding of this sample, the line above would essentially "new" up a WidgetService, which usually produces an warning from the Dart analyzer, and said service variable would actually be an instance of ConcreateWidgetService based on the assignment of the ConcreateWidgetService to the factory constructor of the WidgetService.
Is there a benefit to this approach? From my OOP experience, I've used abstract classes/interfaces to program against when I didn't know the concrete type I would be given. Here it seems we are assigning the concrete type immediately to the abstract factory constructor. I guess my second question would be in this instance why not just use the ConcreteWidgetService directly here instead of repeating all the method signatures?
In your example the benefits are limited but in more complex situations the benefits become more clear.
A factory constructor allows you more control about what the constructor returns. It can return an instance of a subclass or an already existing (cached) instance.
It can return different concrete implementations based on a constructor parameter:
abstract class WidgetService {
WidgetService _cached;
factory WidgetService(String type) {
switch (type) {
case 'a':
return ConcreteWidgetServiceA();
case 'b':
return ConcreteWidgetServiceB();
default:
return _cached ??= DummyWidgetServiceA();
}
}
Widget getWidget();
void saveWidget(Widget widget);
}
Your example seems to be a preparation to be extended eventually to such a more flexible approach.
In Dart, all classes are also automatically interfaces. There's nothing strange about creating a instance of - 'newing up' - an 'interface', because it's actually just creating an instance of a class.
The purpose of some abstract classes is specifically to define an interface, but they have factory constructors that return a 'default implementation' of themselves.
For instance:
void main() {
var v = new Map();
print(v.runtimeType);
}
prints: _InternalLinkedHashMap - the (current) default implementation of Map.
Core library users can create and using instances of Map without knowing or caring about the implementation they actually get. The library authors may change the implementation without breaking anyone's code.
Of course, other classes may implement Map also.
With respect to your code sample, WidgetService _service = new WidgetService(); does not produce an analyzer warning. See this example on DartPad (Note I fixed a couple of errors in your sample).
I was initially confused by:
factory WidgetService() = ConcreteWidgetService;
instead of:
factory WidgetService() => new ConcreteWidgetService();
but it seems to work.

How to deal with hard to express requirements for dependencies?

When doing IoC, I (think that I) understand its use for getting the desired application level functionality by composing the right parts, and the benefits for testability. But at the microlevel, I don't quite understand how to make sure that an object gets dependencies injected that it can actually work with. My example for this is a BackupMaker for a database.
To make a backup, the database needs to be exported in a specific format, compressed using a specific compression algorithm, and then packed together with some metadata to form the final binary. Doing all of these tasks seems to be far from a single responsibility, so I ended up with two collaborators: a DatabaseExporter and a Compressor.
The BackupMaker doesn't really care how the database is exported (e.g. using IPC to a utility that comes with the database software, or by doing the right API calls) but it does care a lot about the result, i.e. it needs to be a this-kind-of-database backup in the first place, in the transportable (version agnostic) format, either of which I don't really know how to wrap in a contract. Neither does it care if the compressor does the compression in memory or on disk, but it has to be BZip2.
If I give the BackupMaker the wrong kinds of exporter or compressor, it will still produce a result, but it will be corrupt - it'll look like a backup, but it won't have the format that it should have. It feels like no other part of the system can be trusted to give it those collaborators, because the BackupMaker won't be able to guarantee to do the right thing itself; its job (from my perspective) is to produce a valid backup and it won't if the circumstances ain't right, and worse, it won't know about it. At the same time, even when writing this, it seems to me that I'm saying something stupid now, because the whole point of single responsibilities is that every piece should do its job and not worry about the jobs of others. If it were that simple though, there would be no need for contracts - J.B. Rainsberger just taught me there is. (FYI, I sent him this question directly, but I haven't got a reply yet and more opinions on the matter would be great.)
Intuitively, my favorite option would be to make it impossible to combine classes/objects in an invalid way, but I don't see how to do that. Should I write horrendously specific interface names, like IDatabaseExportInSuchAndSuchFormatProducer and ICompressorUsingAlgorithmXAndParametersY and assume that no classes implement these if they don't behave as such, and then call it a day since nothing can be done about outright lying code? Should I go as far as the mundane task of dissecting the binary format of my database's exports and compression algorithms to have contract tests to verify not only syntax but behavior as well, and then be sure (but how?) to use only tested classes? Or can I somehow redistribute the responsibilities to make this issue go away? Should there be another class whose responsibility it is to compose the right lower level elements? Or am I even decomposing too much?
Rewording
I notice that much attention is given to this very particular example. My question is more general than that, however. Therefore, for the final day of the bounty, I will try to summarize is as follows.
When using dependency injection, by definition, an object depends on other objects for what it needs. In many book examples, the way to indicate compatibility - the capability to provide that need - is by using the type system (e.g. implementing an interface). Beyond that, and especially in dynamic languages, contract tests are used. The compiler (if present) checks the syntax, and the contract tests (that the programmer needs to remember about) verify the semantics. So far, so good. However, sometimes the semantics are still too simple to ensure that some class/object is usable as a dependency to another, or too complicated to be described properly in a contract.
In my example, my class with a dependency on a database exporter considers anything that implements IDatabaseExportInSuchAndSuchFormatProducer and returns bytes as valid (since I don't know how to verify the format). Is very specific naming and such a very rough contract the way to go or can I do better than that? Should I turn the contract test into an integration test? Perhaps (integration) test the composition of all three? I'm not really trying to be generic but am trying to keep responsibilities separate and maintain testability.
What you have discovered in your question is that you have 2 classes that have an implicit dependency on one another. So, the most practical solution is to make the dependency explicit.
There are a number of ways you could do this.
Option 1
The simplest option is to make one service depend on the other, and make the dependent service explicit in its abstraction.
Pros
Few types to implement and maintain.
The compression service could be skipped for a particular implementation just by leaving it out of the constructor.
The DI container is in charge of lifetime management.
Cons
May force an unnatural dependency into a type where it is not really needed.
public class MySqlExporter : IExporter
{
private readonly IBZip2Compressor compressor;
public MySqlExporter(IBZip2Compressor compressor)
{
this.compressor = compressor;
}
public void Export(byte[] data)
{
byte[] compressedData = this.compressor.Compress(data);
// Export implementation
}
}
Option 2
Since you want to make an extensible design that doesn't directly depend on a specific compression algorithm or database, you can use an Aggregate Service (which implements the Facade Pattern) to abstract the more specific configuration away from your BackupMaker.
As pointed out in the article, you have an implicit domain concept (coordination of dependencies) that needs to be realized as an explicit service, IBackupCoordinator.
Pros
The DI container is in charge of lifetime management.
Leaving compression out of a particular implementation is as easy as passing the data through the method.
Explicitly implements a domain concept that you are missing, namely coordination of dependencies.
Cons
Many types to build and maintain.
BackupManager must have 3 dependencies instead of 2 registered with the DI container.
Generic Interfaces
public interface IBackupCoordinator
{
void Export(byte[] data);
byte[] Compress(byte[] data);
}
public interface IBackupMaker
{
void Backup();
}
public interface IDatabaseExporter
{
void Export(byte[] data);
}
public interface ICompressor
{
byte[] Compress(byte[] data);
}
Specialized Interfaces
Now, to make sure the pieces only plug together one way, you need to make interfaces that are specific to the algorithm and database used. You can use interface inheritance to achieve this (as shown) or you can just hide the interface differences behind the facade (IBackupCoordinator).
public interface IBZip2Compressor : ICompressor
{}
public interface IGZipCompressor : ICompressor
{}
public interface IMySqlDatabaseExporter : IDatabaseExporter
{}
public interface ISqlServerDatabaseExporter : IDatabaseExporter
{}
Coordinator Implementation
The coordinators are what do the job for you. The subtle difference between implementations is that the interface dependencies are explicitly called out so you cannot inject the wrong type with your DI configuration.
public class BZip2ToMySqlBackupCoordinator : IBackupCoordinator
{
private readonly IMySqlDatabaseExporter exporter;
private readonly IBZip2Compressor compressor;
public BZip2ToMySqlBackupCoordinator(
IMySqlDatabaseExporter exporter,
IBZip2Compressor compressor)
{
this.exporter = exporter;
this.compressor = compressor;
}
public void Export(byte[] data)
{
this.exporter.Export(byte[] data);
}
public byte[] Compress(byte[] data)
{
return this.compressor.Compress(data);
}
}
public class GZipToSqlServerBackupCoordinator : IBackupCoordinator
{
private readonly ISqlServerDatabaseExporter exporter;
private readonly IGZipCompressor compressor;
public BZip2ToMySqlBackupCoordinator(
ISqlServerDatabaseExporter exporter,
IGZipCompressor compressor)
{
this.exporter = exporter;
this.compressor = compressor;
}
public void Export(byte[] data)
{
this.exporter.Export(byte[] data);
}
public byte[] Compress(byte[] data)
{
return this.compressor.Compress(data);
}
}
BackupMaker Implementation
The BackupMaker can now be generic as it accepts any type of IBackupCoordinator to do the heavy lifting.
public class BackupMaker : IBackupMaker
{
private readonly IBackupCoordinator backupCoordinator;
public BackupMaker(IBackupCoordinator backupCoordinator)
{
this.backupCoordinator = backupCoordinator;
}
public void Backup()
{
// Get the data from somewhere
byte[] data = new byte[0];
// Compress the data
byte[] compressedData = this.backupCoordinator.Compress(data);
// Backup the data
this.backupCoordinator.Export(compressedData);
}
}
Note that even if your services are used in other places than BackupMaker, this neatly wraps them into one package that can be passed to other services. You don't necessarily need to use both operations just because you inject the IBackupCoordinator service. The only place where you might run into trouble is if using named instances in the DI configuration across different services.
Option 3
Much like Option 2, you could use a specialized form of Abstract Factory to coordinate the relationship between concrete IDatabaseExporter and IBackupMaker, which will fill the role of the dependency coordinator.
Pros
Few types to maintain.
Only 1 dependency to register in the DI container, making it simpler to deal with.
Moves lifetime management into the BackupMaker service, which makes it impossible to misconfigure DI in a way that will cause a memory leak.
Explicitly implements a domain concept that you are missing, namely coordination of dependencies.
Cons
Leaving compression out of a particular implementation requires you implement the Null object pattern.
The DI container is not in charge of lifetime management and each dependency instance is per request, which may not be ideal.
If your services have many dependencies, it may become unwieldy to inject them through the constructor of the CoordinationFactory implementations.
Interfaces
I am showing the factory implementation with a Release method for each type. This is to follow the Register, Resolve, and Release pattern which makes it effective for cleaning up dependencies. This becomes especially important if 3rd parties could implement the ICompressor or IDatabaseExporter types because it is unknown what kinds of dependencies they may have to clean up.
Do note however, that the use of the Release methods is totally optional with this pattern and excluding them will simplify the design quite a bit.
public interface IBackupCoordinationFactory
{
ICompressor CreateCompressor();
void ReleaseCompressor(ICompressor compressor);
IDatabaseExporter CreateDatabaseExporter();
void ReleaseDatabaseExporter(IDatabaseExporter databaseExporter);
}
public interface IBackupMaker
{
void Backup();
}
public interface IDatabaseExporter
{
void Export(byte[] data);
}
public interface ICompressor
{
byte[] Compress(byte[] data);
}
BackupCoordinationFactory Implementation
public class BZip2ToMySqlBackupCoordinationFactory : IBackupCoordinationFactory
{
public ICompressor CreateCompressor()
{
return new BZip2Compressor();
}
public void ReleaseCompressor(ICompressor compressor)
{
IDisposable disposable = compressor as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
public IDatabaseExporter CreateDatabaseExporter()
{
return new MySqlDatabseExporter();
}
public void ReleaseDatabaseExporter(IDatabaseExporter databaseExporter)
{
IDisposable disposable = databaseExporter as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}
public class GZipToSqlServerBackupCoordinationFactory : IBackupCoordinationFactory
{
public ICompressor CreateCompressor()
{
return new GZipCompressor();
}
public void ReleaseCompressor(ICompressor compressor)
{
IDisposable disposable = compressor as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
public IDatabaseExporter CreateDatabaseExporter()
{
return new SqlServerDatabseExporter();
}
public void ReleaseDatabaseExporter(IDatabaseExporter databaseExporter)
{
IDisposable disposable = databaseExporter as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}
BackupMaker Implementation
public class BackupMaker : IBackupMaker
{
private readonly IBackupCoordinationFactory backupCoordinationFactory;
public BackupMaker(IBackupCoordinationFactory backupCoordinationFactory)
{
this.backupCoordinationFactory = backupCoordinationFactory;
}
public void Backup()
{
// Get the data from somewhere
byte[] data = new byte[0];
// Compress the data
byte[] compressedData;
ICompressor compressor = this.backupCoordinationFactory.CreateCompressor();
try
{
compressedData = compressor.Compress(data);
}
finally
{
this.backupCoordinationFactory.ReleaseCompressor(compressor);
}
// Backup the data
IDatabaseExporter exporter = this.backupCoordinationFactory.CreateDatabaseExporter();
try
{
exporter.Export(compressedData);
}
finally
{
this.backupCoordinationFactory.ReleaseDatabaseExporter(exporter);
}
}
}
Option 4
Create a guard clause in your BackupMaker class to prevent non-matching types from being allowed, and throw an exception in the case they are not matched.
In C#, you can do this with attributes (which apply custom metadata to the class). Support for this option may or may not exist in other platforms.
Pros
Seamless - no extra types to configure in DI.
The logic for comparing whether types match could be expanded to include multiple attributes per type, if needed. So a single compressor could be used for multiple databases, for example.
100% of invalid DI configurations will cause an error (although you may wish to make the exception specify how to make the DI configuration work).
Cons
Leaving compression out of a particular backup configuration requires you implement the Null object pattern.
The business logic for comparing types is implemented in a static extension method, which makes it testable but impossible to swap with another implementation.
If the design is refactored so that ICompressor or IDatabaseExporter are not dependencies of the same service, this will no longer work.
Custom Attribute
In .NET, an attribute can be used to attach metadata to a type. We make a custom DatabaseTypeAttribute that we can compare the database type name with two different types to ensure they are compatible.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public DatabaseTypeAttribute : Attribute
{
public DatabaseTypeAttribute(string databaseType)
{
this.DatabaseType = databaseType;
}
public string DatabaseType { get; set; }
}
Concrete ICompressor and IDatabaseExporter Implementations
[DatabaseType("MySql")]
public class MySqlDatabaseExporter : IDatabaseExporter
{
public void Export(byte[] data)
{
// implementation
}
}
[DatabaseType("SqlServer")]
public class SqlServerDatabaseExporter : IDatabaseExporter
{
public void Export(byte[] data)
{
// implementation
}
}
[DatabaseType("MySql")]
public class BZip2Compressor : ICompressor
{
public byte[] Compress(byte[] data)
{
// implementation
}
}
[DatabaseType("SqlServer")]
public class GZipCompressor : ICompressor
{
public byte[] Compress(byte[] data)
{
// implementation
}
}
Extension Method
We roll the comparison logic into an extension method so every implementation of IBackupMaker automatically includes it.
public static class BackupMakerExtensions
{
public static bool DatabaseTypeAttributesMatch(
this IBackupMaker backupMaker,
Type compressorType,
Type databaseExporterType)
{
// Use .NET Reflection to get the metadata
DatabaseTypeAttribute compressorAttribute = (DatabaseTypeAttribute)compressorType
.GetCustomAttributes(attributeType: typeof(DatabaseTypeAttribute), inherit: true)
.SingleOrDefault();
DatabaseTypeAttribute databaseExporterAttribute = (DatabaseTypeAttribute)databaseExporterType
.GetCustomAttributes(attributeType: typeof(DatabaseTypeAttribute), inherit: true)
.SingleOrDefault();
// Types with no attribute are considered invalid even if they implement
// the corresponding interface
if (compressorAttribute == null) return false;
if (databaseExporterAttribute == null) return false;
return (compressorAttribute.DatabaseType.Equals(databaseExporterAttribute.DatabaseType);
}
}
BackupMaker Implementation
A guard clause ensures that 2 classes with non-matching metadata are rejected before the type instance is created.
public class BackupMaker : IBackupMaker
{
private readonly ICompressor compressor;
private readonly IDatabaseExporter databaseExporter;
public BackupMaker(ICompressor compressor, IDatabaseExporter databaseExporter)
{
// Guard to prevent against nulls
if (compressor == null)
throw new ArgumentNullException("compressor");
if (databaseExporter == null)
throw new ArgumentNullException("databaseExporter");
// Guard to prevent against non-matching attributes
if (!DatabaseTypeAttributesMatch(compressor.GetType(), databaseExporter.GetType()))
{
throw new ArgumentException(compressor.GetType().FullName +
" cannot be used in conjunction with " +
databaseExporter.GetType().FullName)
}
this.compressor = compressor;
this.databaseExporter = databaseExporter;
}
public void Backup()
{
// Get the data from somewhere
byte[] data = new byte[0];
// Compress the data
byte[] compressedData = this.compressor.Compress(data);
// Backup the data
this.databaseExporter.Export(compressedData);
}
}
If you decide on one of these options, I would appreciate if you left a comment as to which one you go with. I have a similar situation in one of my projects, and I am leaning toward Option 2.
Response to your Update
Is very specific naming and such a very rough contract the way to go or can I do better than that? Should I turn the contract test into an integration test? Perhaps (integration) test the composition of all three? I'm not really trying to be generic but am trying to keep responsibilities separate and maintain testability.
Creating an integration test is a good idea, but only if you are certain that you are testing the production DI configuration. Although it also makes sense to test it all as a unit to verify it works, it doesn't do you much good for this use case if the code that ships is configured differently than the test.
Should you be specific? I believe I have already given you a choice in that matter. If you go with the guard clause, you don't have to be specific at all. If you go with one of the other options, you have a good compromise between specific and generic.
I know you stated that you are not intentionally trying to be generic, and it is good to draw the line somewhere to ensure a solution is not over-engineered. On the other hand, if the solution has to be redesigned because an interface was not generic enough that is not a good thing either. Extensibility is always a requirement whether it is specified up front or not because you never really know how business requirements will change in the future. So, having a generic BackupMaker is definitely the best way to go. The other classes can be more specific - you just need one seam to swap implementations if future requirements change.
My first suggestion would be to critically think if you need to be that generic: You have a concrete problem to solve, you want to backup a very specific database into a specific format. Is there any benefit you get by solving the problem for arbitary databases and arbitary formats? What you surely get of a generic solution is boilerplate code and increased complexity (people understand concrete problems, not generic ones).
If this applies to you, then my suggestion would be to not let your DatabaseExporter accept interfaces, but instead only concrete implementations. There are enough modern tools out there which will also allow you mocking concrete classes, so testability is not an argument for using interfaces here aswell.
on the other hand, if you do have to backup several databases with different strategies, then I would probably introduce something like a
class BackupPlan {
public DatabaseExporter exporter() {/**...*/}
public Compressor compressor() {/** ... */}
}
then your BackupMaker will get passed one BackupPlan, specifying which database to be compressed with which algorithm.
Your question is emphasizing the fact that object composition is very important and that the entity that is responsible for such composition (wiring) has a big responsibility.
Since you already have a generic BackupMaker, I would suggest that you keep it this way, and push the big responsibility of making sure that the right composition of objects (to solve the specific problem) is done in the composition root.
Readers of your application source code (you and your team members), would have a single place (the composition root) to understand how you compose your objects to solve your specific problem by using the generic classes (e.g. BackupMaker).
Put in other words, the composition root is where you decide on the specifics. Its where you use the generic to create the specific.
To reply on the comment:
which should know what about those dependencies?
The composition root needs to know about everything (all the dependencies) since it is creating all the objects in the application and wiring them together. The composition root knows what each piece of the puzzle does and it connects them together to create a meaningful application.
For the BackupMaker, it should only care about just enough to be able to do its single responsibility. In your example, its single (simple) responsibility (as it seems to me) is to orchestrate the consumption of other objects to create a backup.
As long as you are using DI, a class will never be sure that its collaborator will behave correctly, only the composition root will. Consider this simple and extreme example of an IDatabaseExporter implementation (assume that the developer actually gave this class this name, and that he intentionally implemented it this way):
public class StupidDisastrousDatabaseExporter : IDatabaseExporter
{
public ExportedData Export()
{
DoSomethingStupidThatWillDeleteSystemDataAndMakeTheEnterpriseBroke();
...
}
private void DoSomethingStupidThatWillDeleteSystemDataAndMakeTheEnterpriseBroke()
{
//do it
...
}
}
Now, the BackupMaker will never know that it is consuming a stupid and disastrous database exporter, only the composition root does. We can never blame the programmer that wrote the BackupMaker class for this disastrous mistake (or the programmer who designed the IDatabaseExporter contract). But the programmer(s) that are composing the application in the composition root are blamed if they inject a StupidDisastrousDatabaseExporter instance into the constructor of BackupMaker.
Of course, no one should have written the StupidDisastrousDatabaseExporter class in the first place, but I gave you an extreme example to show you that a contract (interface) can never (and should never) guarantee every aspect about its implementors. It should just say enough.
Is there a way to express IDatabaseExporter in such a way that guarantees that implementors of such interface will not make stupid or disastrous actions? No.
Please note that while the BackupMaker is dealing with contracts (no 100% guarantees), the composition root is actually dealing with concrete implementation classes. This gives it the great power (and thus the great responsibility) to guarantee the composition of the correct object graph.
how do I make sure that I'm composing in a sensible way?
You should create automated end-to-end tests for the object graph created by the composition root. Here you are making sure that the composition root has done its big responsibility of composing the objects in a correct way. Here you can test the exact details that you wanted (like that the backup result was in some exact format/details).
Take a look at this article for an approach to automated testing using the Composition Root.
I believe this may be a problem that occurs when focusing too much on object models, at the exclusion of function compositions. Consider the first step in a naive function decomposition (function as in f : a -> b):
exporter: data -> (format, memory), or exception
compressor: memory -> memory, or exception
writer: memory -> side-effect, or exception
backup-maker: (data, exporter, compressor, writer) -> backup-result
So backup-maker, the last function, can be parametized with those three functions, assuming I've considered your use-case correctly, and if the three parameters have the same input and output types, e.g. format, and memory, despite their implementation.
Now, "the guts", or a possible decomposition (read right to left) of backup-maker, with all functions bound, taking data as the argument, and using the composition operator ".":
backup-maker: intermediate-computation . writer . intermediate-computation . compressor . intermediate-computation . exporter
I especially want to note that this model of architecture can be expressed later as either object interfaces, or as first-class functions, e.g. c++ std::function.
Edit: It can also be refined to terms of generics, where memory is a generic type argument, to provide type safety where wanted. E.g.
backup-maker<type M>: (data, exporter<M>, compressor<M>, writer<M>) -> ..
More information about the technique and benefits of Function Decomposition can be found here:
http://jfeltz.com/posts/2015-08-30-cost-decreasing-software-architecture.html
Your requirements seem contradictory:
You want to be specific (allowing only a subset (or only one ?) of combinations)
But you also want to be generic by using interfaces, DI, etc.
My advice is to keep things simple (in your case it means don't try to be generic) until your code evolve.
Only when your code will evolve, refactor in a more generic way. The code below shows a compromise between generic/specific:
public interface ICompressor {
public byte[] compress(byte[] source); //Note: the return type and type argument may not be revelant, just for demonstration purpose
}
public interface IExporter {
public File export(String connectionString); //Note: the return type and type argument may not be revelant, just for demonstration purpose
}
public final class Bzip2 implements ICompressor {
#Override
public final byte[] compress(byte[] source) {
//TODO
}
}
public final class MySQL implements IExporter {
#Override
public final File export(String connnectionString) {
//TODO
}
}
public abstract class ABackupStrategy {
private final ICompressor compressor;
private final IExporter exporter;
public ABackupStrategy(final ICompressor compressor, final IExporter exporter) {
this.compressor = compressor;
this.exporter = exporter;
}
public final void makeBackup() {
//TODO: compose with exporter and compressor to make your backup
}
}
public final class MyFirstBackupStrategy extends ABackupStrategy {
public MyFirstBackupStrategy(final Bzip2 compressor, final MySQL exporter) {
super(compressor, exporter);
}
}
With ICompressor and IExporter, you can easily add other compression algorithm, other database from which to export.
With ABackupStrategy, you can easily define a new allowed combination of concrete compressor/exporter by inheriting it.
Drawback: I had to make ABackupStrategy abstract without declaring any abstract method, which is in contradiction with the OOP-principles.

How do you implement type-specific functionality when using polymorphism does NOT make sense?

A common red flag that an OOP language is not being leveraged properly looks like this:
if (typeof(x) == T1)
{
DoSomethingWithT1(x);
}
else if (typeof(x) == T2)
{
DoSomethingWithT2(x);
}
The standard "fix" for such design issues is to make T1 and T2 both share an interface, either through inheritance of a base type or implementation of a common interface (in languages that support it); for example, in C# a solution might be:
public interface IT
{
void DoSomething();
}
However, sometimes you want to implement functionality that differs based on the type of an object but that functionality does not belong within that object's type; thus polymorphism seems the wrong way to go.
For example, consider the case of a UI that provides a view of a given clump of data. Supposing this view is capable of rendering various layouts and controls depending on the type of data being presented, how would you implement this type-specific rendering without a bunch of if/else statements?
For reasons that I hope are obvious, putting the rendering logic in the type itself strikes me as a very bad decision in this case. On the other hand, without coupling the type of data object to its visual presentation I have a hard time seeing how the if/else scenario is avoided.
Here's a concrete example: I work on a trading application which utilizes many different pricing models for various market products. These different models are represented by types inheriting from a common PricingModel base; and each type is associated with a completely different set of parameters. When the user wants to view the parameters for a particular pricing model (for a particular product), currently these are displayed by a form which detects the type of the model and displays an appropriate set of controls. My question is how this could be implemented more elegantly than it is currently (with a big if/else block).
I realize this probably seems like a very basic question; it's just one of those gaps in my knowledge (of solid OOP principles? design patterns? common sense?) that I figured it's about time to fix.
We are injecting (Spring.Net) such functionality into dictionaries by type.
IDictionary<Type, IBlahImplementor> blahImplementors;
blahImplementors[thingy.GetType()].Do(thingy);
This dictionary could be managed by a kind of repository which provides the functionality.
As an implementation detail, the implementor usually knows the type it depends on an can provide it itself:
interface IBlahImplementor
{
Type ForType { get; }
void Do(object thingy);
}
Then it is added to the dictionary like this:
IEnumerably<IBlahImplementor> blahImplementors;
foreach (var implementor in blahImplementors)
{
blahImplementors.Add(implementor.ForType, implementor);
}
Remark: IMHO, it is very important to understand that some things do NOT belong into a class, even if providing subtype-specific implementations would make life much easier.
Edit: Finally understood your concrete example.
It is actually about instancing the right UI control to show the pricing models parameters. It should be possible with the pattern I described above. If you don't have a single UI control for a pricing model, you either create it or you write a UI configurer or something like this which sets up the required controls.
interface IPricingModelUiConfigurer
{
Type PricingModelType { get; }
void SetupUi(Control parent, IPricingModel model);
}
you can use common interface approach as you describe and Command pattern to trigger methods with "functionality does not belong within that object's type". I think this won't break solid OOP principles.
What you described is pretty much exactly the use case for the Visitor Pattern.
EDIT: For your concrete example, you could apply the visitor pattern like this:
// interface used to add external functionality to pricing models
public interface PricingModelVisitor {
void visitPricingModel1(PricingModel1 m);
void visitPricingModel2(PricingModel2 m);
...
}
// your existing base-class, with added abstract accept() method to accept a visitor
public abstract class PricingModelBase {
public abstract void accept(PricingModelVisitor v);
...
}
// concrete implementations of the PricingModelBase implement accept() by calling the
// appropriate method on the visitor, passing themselves as the argument
public class PricingModel1 : PricingModelBase {
public void accept(PricingModelVisitor v) { v.visitPricingModel1(this); }
...
}
public class PricingModel2 : PricingModel {
public void accept(PricingModelVisitor v) { v.visitPricingModel2(this); }
...
}
// concrete implementation of the visitor interface, in this case with the new
// functionality of adding the appropriate controls to a parent control
public class ParameterGuiVisitor : PricingModelVisitor {
private Control _parent;
public ParameterGuiVisitor(Control parent) { _parent = parent; }
visitPricingModel1(PricingModel1 m) {
// add controls to _parent for PricingModel1
}
visitPricingModel2(PricingModel2 m) {
// add controls to _parent for PricingModel1
}
}
now, instead of using a big if-else block when you need to display the edit-controls for the parameters of a specific subtype of PricingModelVisitor, you can simply call
somePricingModel.accept(new ParameterGuiVisitor(parentControl))
and it will populate the appropriate GUI for you.

Alternative to the visitor pattern?

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!