0

I've tried to refactor some code based on the Open-Closed Principle, but I just don't seem to be able to get the following classes right when it comes to applying design patterns. (I apologize for the many classes outlined below - I have reduced them as much as possible, but the rest is needed to show you my design).

The set-up consists of the following classes:

public interface IPortFactory
{
    IPort CreatePort(int id, PortDetails details);
}

public class PtpPortFactory : IPortFactory
{
    public IPort CreatePort(int id, PortDetails details)
    {
        var ptpPortDetails = details as PtpPortDetails;
        if (ptpPortDetails == null)
        {
            throw new ArgumentException("Port details does not match ptp ports", "details");
        }

        return new PtpPort(id, FiberCapability.FromValue(ptpPortDetails.Capability));
    }
}

public interface IPort
{
    int Id { get; }
}

public interface IInternetPort : IPort
{
    bool OfCapability(FiberCapability capability);
}

public class PtpPort : IInternetPort
{
    private readonly FiberCapability _capability;

    public PtpPort(int id, FiberCapability capability)
    {
        _capability = capability;
        Id = id;
    }

    public int Id { get; private set; }

    public bool OfCapability(FiberCapability capability)
    {
        return capability.Equals(_capability);
    }
}

Besides PtpPort, I have PonPort, which also implements IInternetPort, whereas CatvPort just implements IPort.

Already in this code, I think there's sign of code smell. In CreatePort in PtpPortFactory, the pretty thing would be for it to accept PtpPortDetails (which inherits from PortDetails) instead of having to cast. However, if I do so, I cannot create a PonPortFactory, which also implements IPortFactory, as these ports would need PonPortDetails. Or a CatvPortFactory for that matter.

Another code smell appears when I use the port factory:

PortType portType = command.PortType;
IPortFactory portFactory = portType.GetPortFactory();
var portsToSelectFrom = ports.Select(port => (IInternetPort) portFactory.CreatePort(port.Id, port.PortDetails)).ToList();

I would really like not having to do the downcast from IPort to IInternetPort, but simply have CreatePort return IInternetPort.

The last bit of information needed to understand the above is probably the following class (based on Jimmy Bogards Enumeration class):

public abstract class PortType : Enumeration<PortType, int>
{
    public static readonly PortType Ptp = new PtpPortType();
    public static readonly PortType Pon = new PonPortType();
    public static readonly PortType Catv = new CatvPortType();

    protected PortType(int value, string description)
        : base(value, description) { }

    public abstract IPortFactory GetPortFactory();

    private class CatvPortType : PortType
    {
        public CatvPortType() : base(2, "catv") { }

        public override IPortFactory GetPortFactory()
        {
            return new CatvPortFactory();
        }
    }

    private class PonPortType : PortType
    {
        public PonPortType() : base(1, "pon") { }

        public override IPortFactory GetPortFactory()
        {
            throw new NotImplementedException("Pon ports are not supported");
        }
    }

    private class PtpPortType : PortType
    {
        public PtpPortType() : base(0, "ptp") { }

        public override IPortFactory GetPortFactory()
        {
            return new PtpPortFactory();
        }
    }
}

I really hope that someone can help me along the way (I've tried introducing generics, but seem to always hit the barrier of C# not supporting return type covariance).

Moreover, any other tips 'n tricks to help me along my path to write better code would be greatly appreciated.

Update

Due to request in comment, I've added more code below.

public Port Handle(TakeInternetPortCommand command)
{
    var portLocatorService = new PortLocatorService();
    IList<Port> availablePorts = portLocatorService.FindAvailablePorts(command.Pop, command.PortType);

    PortType portType = command.PortType;
    IPortFactory portFactory = portType.GetPortFactory();
    var portsToSelectFrom = ports.Select(port => (IInternetPort) portFactory.CreatePort(port.Id, port.PortDetails)).ToList();

    IPort port = _algorithm.RunOn(portsToSelectFrom);
    Port chosenPort = availablePorts.First(p => p.Id == port.Id);
    chosenPort.Take(command.Spir);
    _portRepository.Add(chosenPort);
    return chosenPort;
}

Do not be confused by the fact that all of a sudden there's also a Port type. This is an aggregate in another bounded context (in the sense of DDD). The algorithm needs to take as input a list of IInternetPort, as it uses the method OfCapability internally to select a port. However, when the algorithm has selected the correct port, we are merely interested in the Id, hence the return type is just an IPort.

SabrinaMH
  • 221
  • 1
  • 3
  • 11
  • 5
    When you have working code and seek help to refactor or get advice, the [Code Review](http://codereview.stackexchange.com/) Stack Exchange is best suited for that job. – Pierre-Luc Pineault Sep 14 '15 at 17:24
  • Who is the consumer/client of `IPort`, `IInternetPort`, and `IPortFactory`? How/When do you decide to use `PtpPort`, `PonPort` or `CatvPort`? Are they all used in the same application instance? Or do you decide to use only one of them based on some application configuration? – Yacoub Massad Sep 15 '15 at 00:42
  • @Pierre-LucPineault, sorry for posting in the wrong forum. I'll remember Code Review Stack Exchange next time! – SabrinaMH Sep 15 '15 at 15:48
  • @YacoubMassad, thanks for your in-depth questions. The consumer/client of `IPort`, `IInternetPort` and `IPortFactory`, i.e. the place where the three lines just below "Another code smell appears when I use the port factory" in my original post, is a command handler within the same application. The type of port is sent as a string in the command (ultimately it's the client submitting this as a part of the body of a post request). – SabrinaMH Sep 15 '15 at 15:50
  • Is there any specific reason for having `IPort` as an interface? – Abdul Rahman K Sep 16 '15 at 00:58
  • What is the `ports` array? and why are you casing to `IInternetPort`? How do you know for sure that the returned `IPort` is actually a `IInternetPort`? The 3 lines you pointed me to are the consumer or `IPortFactory`, but who is the consumer of `IPort` and `IInternetPort`? Can you show more code? – Yacoub Massad Sep 16 '15 at 06:56
  • @abdulrahmank, are you suggesting an abstract class instead? If so, the only reason for the interface was because I've learned that "inheritance is bad" and thus should be avoided :-$ – SabrinaMH Sep 16 '15 at 17:47
  • @YacoubMassad, I've added more code. Thank you for investing so much time in my post! – SabrinaMH Sep 16 '15 at 17:47
  • Why do you have an Id property in IPort? Just to be able to go back to the original Port object after you run the algorithm? So, _algorithm.RunOn is the actual consumer of IPort and IInternetPort? Does it need IPort.Id to be able to do its job? I am guessing that it only needs to check capabilities, right? – Yacoub Massad Sep 17 '15 at 08:49
  • @SabrinaMH But when ever their is a "is a" relationship we should opt inheritance as that is the main concept of OOP namely Generalisation . – Abdul Rahman K Sep 18 '15 at 01:27
  • @YacoubMassad, you are completely correct in assuming that the algorithm only needs the capabilities to do its job. It only needs the id such that it can tell the consumer which port it selected. – SabrinaMH Sep 18 '15 at 14:29
  • @abdulrahmank, you are probably right :) But does this help me in any way here? – SabrinaMH Sep 18 '15 at 14:29
  • @SabrinaMH you said you had to cast only for the purpose of calling the methods in that class? – Abdul Rahman K Sep 19 '15 at 05:33
  • @abdulrahmank, true because `CreatePort` returns `IPort`, but this wouldn't change even if I used inheritance. Then I would still have to cast to be able to access `OfCapability`, right? – SabrinaMH Sep 20 '15 at 12:10

2 Answers2

2

Here is how I understand the problem you are trying to solve (the business problem, not the design problem):

You have a list of ports, you want to execute some algorithm over the list that will choose a single port from the list (based on some criteria that is specific to the algorithm).

Here I will suggest a way to model this. I am going to assume that you have the following input class:

public class PortInput
{
    public int Id { get; set; }

    public PortDetails PortDetails { get; set; }
}

This corresponds to part of your Port class in your question.

And here you have the interface for the algorithm:

public interface IPortSelectionAlgorithm
{
    int SelectPort(PortInput[] port_inputs);
}

Please note that this interface models the problem that we want to solve. i.e., given a list of ports -> we need to choose one

And here is an implementation for such algorithm interface:

public class PortSelectionAlgorithm : IPortSelectionAlgorithm
{
    private readonly ICapabilityService<PortDetails> m_CapabilityService;

    public PortSelectionAlgorithm(ICapabilityService<PortDetails> capability_service)
    {
        m_CapabilityService = capability_service;
    }

    public int SelectPort(PortInput[] port_inputs)
    {
        //Here you can use m_CapabilityService to know if a specific port has specific capability

        ...
    }
}

What this implementation is declaring is that it requires some service that knows how to obtain the capabilities of ports based on port details. Here is the definition of such service contract:

public interface ICapabilityService<TDetails> where TDetails : PortDetails
{
    bool OfCapability(TDetails port_details, FiberCapability capability);
}

For each port type we can create an implementation of such service, like this:

public class PtpPortCapabilityService: ICapabilityService<PtpPortDetails>
{
    public bool OfCapability(PtpPortDetails port_details, FiberCapability capability)
    {
        ...
    }
}

public class CatvPortCapabilityService : ICapabilityService<CatvPortDetails>
{
    public bool OfCapability(CatvPortDetails port_details, FiberCapability capability)
    {
        ...
    }
}

public class PonPortCapabilityService : ICapabilityService<PonPortDetails>
{
    public bool OfCapability(PonPortDetails port_details, FiberCapability capability)
    {
        //If such kind of port does not have any capability, simply return false
        ...
    }
}

And then, we can create another implementation that uses these individual services to be able to tell the capabilities of any port:

public class PortCapabilityService : ICapabilityService<PortDetails>
{
    private readonly ICapabilityService<PtpPortDetails> m_PtpPortCapabilityService;
    private readonly ICapabilityService<CatvPortDetails> m_CatvPortCapabilityService;
    private readonly ICapabilityService<PonPortDetails> m_PonPortCapabilityService;

    public PortCapabilityService(ICapabilityService<PtpPortDetails> ptp_port_capability_service, ICapabilityService<CatvPortDetails> catv_port_capability_service, ICapabilityService<PonPortDetails> pon_port_capability_service)
    {
        m_PtpPortCapabilityService = ptp_port_capability_service;
        m_CatvPortCapabilityService = catv_port_capability_service;
        m_PonPortCapabilityService = pon_port_capability_service;
    }

    public bool OfCapability(PortDetails port_details, FiberCapability capability)
    {
        PtpPortDetails ptp_port_details = port_details as PtpPortDetails;

        if (ptp_port_details != null)
            return m_PtpPortCapabilityService.OfCapability(ptp_port_details, capability);

        CatvPortDetails catv_port_details = port_details as CatvPortDetails;

        if (catv_port_details != null)
            return m_CatvPortCapabilityService.OfCapability(catv_port_details, capability);

        PonPortDetails pon_port_details = port_details as PonPortDetails;

        if (pon_port_details != null)
            return m_PonPortCapabilityService.OfCapability(pon_port_details, capability);

        throw new Exception("Unknown port type");
    }
}

As you can see, no class knows about the port ID except the algorithm class. The classes that determine capabilities do not know about port IDs, because the can do their jobs without them.

Another thing to note is that you do not need to instantiate a new capability service each time you run the algorithm. This is in contrast to the IInternetPort implementations described in your question, where you create a new instance every time you want to execute the algorithm. And I guess you did that because each instance is bound to a different ID.

These classes use dependency injection. You should compose them to be able to use them. You should do this in the Composition Root.

Here is how you can use Pure DI for such composition:

IPortSelectionAlgorithm algorithm =
    new PortSelectionAlgorithm(
        new PortCapabilityService(
            new PtpPortCapabilityService(),
            new CatvPortCapabilityService(),
            new PonPortCapabilityService()));
Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
  • Wow! What an impressive answer. I follow your ideas, but doesn't the method `OfCapability` go against the open-closed principle? If the three port types were to have more methods like `OfCapability`, I would have to modify each of the services when adding a new port. Also, as I'm trying to apply DDD, I would very much like for my domain classes to contain the business logic - and here it is put away in a service, namely `PortCapabilityService`. This comment is in no way meant to be negative - I'm just trying to see if your suggestions can be shaped in this direction :) – SabrinaMH Sep 20 '15 at 12:16
  • If you need to add another method like OfCapability (e.g. OfAttribute), you can create another service contract and create other services to handle such method. I agree with you that PortCapabilityService violates the open-closed principle. When you add more port types, you need to modify it. I am not sure if this can be fixed in a clean way. Regarding DDD, I only know little about it. Are your port classes in your question (e.g., PtpPort) considered DDD entities? I am guessing not, since you create them every time you run the algorithm just to be able to calculate the capabilities. – Yacoub Massad Sep 21 '15 at 09:03
  • the port classes such as `PtpPort` isn't a DDD entity, but a value object. However, I'd still prefer to have as much logic as possible regarding these objects placed on the objects themselves. Once again, thank you so much for your effort!! It amazes me how much people are willing to spend of their own time to try and help others :) – SabrinaMH Sep 22 '15 at 15:57
1

I think there's sign of code smell. In CreatePort in PtpPortFactory, the pretty thing would be for it to accept PtpPortDetails (which inherits from PortDetails) instead of having to cast.

No, It's fine because dependencies should be on absctration and not implementations. So in this case passing PortDetails is fine.

The smell I see is here:

public interface IPort
{
    int Id { get; }
}

public interface IInternetPort : IPort
{
    bool OfCapability(FiberCapability capability);
}

Interfaces are basically used for defining behavior. And you're using properties in an Interface looks suspicious to me.

  • Inheritance describes an is-a relationship.
  • Implementing an interface describes a can-do relationship.

Here you're dealing AbstractFactory pattern. Let say, you can have abstract factory BasePortFactory which can-do what IPortFactory is declaring. So You should return BasePortFactory from factory method. But it's again a choice of matter when designing a solution.

Similary the CreatePort method should expose return type either base class or interface based on if you want to use is-a over can-do stuff.

Update

This sample wouldn't be a best fit for your scenario but this is to depict the idea that I shared:

public interface IInternetPort
{
    bool OfCapability(FiberCapability capability);
}

/// <summary>
/// This class can be a replacement of (IPort) interface. Each port is enabled for query via IInternetPort.
/// As a default behavior every port is not Internet enabled so OfCapability would return false.
/// Note: If you want you can still keep the IPort interface as Marker interface. 
/// /// </summary>
public abstract class Port : IInternetPort
{
    public int Id { get; private set; }

    public Port(int Id)
    {
        this.Id = Id;
    }

    public virtual bool OfCapability(FiberCapability capability)
    {
        // Default port is not internet capable
        return false; 
    }
}

/// <summary>
/// This class is-a <see cref="Port"/> and can provide capability checker.
/// Overiding the behavior of base for "OfCapability" would enable this port for internet.
/// </summary>
public class PtpPort : Port
{
    private readonly FiberCapability _capability;

    public PtpPort(int id, FiberCapability capability) : base(id)
    {
        _capability = capability;
    }

    public override bool OfCapability(FiberCapability capability)
    {
        return capability.Equals(_capability);
    }
}

/// <summary>
/// this test class doesn't need to implement or override OfCapability method
/// still it will be act like any other port.
/// | TestPort port = new TestPort(22);
/// | port.OfCapability(capability);
/// </summary>
public class TestPort : Port
{
    public TestPort(int id): base(id) { }
}

Here's the factories that would need to change the signature of method to return Port instead of IPort.

public interface IPortFactory
{
    Port CreatePort(int id, PortDetails details);
}

public class PtpPortFactory : IPortFactory
{
    public Port CreatePort(int id, PortDetails details)
    {
        var ptpPortDetails = details as PtpPortDetails;
        if (ptpPortDetails == null)
        {
            throw new ArgumentException("Port details does not match ptp ports", "details");
        }

        return new PtpPort(id, FiberCapability.FromValue(ptpPortDetails.Capability));
    }
}

Now this line won't need any external cast.

var portsToSelectFrom = ports.Select(port => portFactory.CreatePort(port.Id, port.PortDetails)).ToList();

P.S. - These type of questions should be asked on code review or programmer.

Community
  • 1
  • 1
vendettamit
  • 14,315
  • 2
  • 32
  • 54
  • "And you're using properties in an Interface looks suspicious to me". Why did you say that ? Could you please explain? – Guanxi Sep 14 '15 at 20:53
  • Just because it's possible doesn't mean it's a best practice. It could be a bad practice if they do something that is non trivial in the implementation. If you understand **Can-do** behavior you would avoid this practice. It's all opinion based. I didn't said it's **wrong**, It was my own opinion for the suspicion. I don't see a reason for getting -1. – vendettamit Sep 14 '15 at 21:04
  • my opinion, your message it is not an answer, that's why you possibly have got -1 (it wasn't me) – jungle_mole Sep 14 '15 at 22:09
  • Because this is not a question it's just asking for best practices and code review stuff. :) – vendettamit Sep 14 '15 at 22:51
  • @vendettamit, I see interfaces as Contract rather than just Can-do behavior and this statement of yours seems to be misleading to me. I would suggest going through this post - http://stackoverflow.com/questions/972392/interface-should-not-have-properties – Guanxi Sep 15 '15 at 13:52
  • @Guanxi Exactly! As I already said there's no stopping. Properties are set of getter/setter(s) method to expose class attributes not actions/methods. Only C# language have properties, What if this problem was in other language that doesn't have properties? .. And again it's all depends on the problem and way of implementation that design is going to solve. If it's not harming the system and it's not even best practice I would say go for it. Opinions would be different on this topic from different people. One can not simply agree on disagree on this. – vendettamit Sep 15 '15 at 14:04
  • @vendettamit, thanks for your suggestions! If you have time, could you perhaps try to show in code how you would implement the above? I have a hard time seeing how to actually implement your hints... – SabrinaMH Sep 15 '15 at 16:37
  • @SabrinaMH udpated the answer. Hope this will give you a clear picture of Idea I shared. – vendettamit Sep 15 '15 at 18:12
  • @vendettamit, thank you so much for the update and all your help. I've given it quite some thoughts and can see how this approach could be really useful in my case - and it would solve my problems :) – SabrinaMH Sep 18 '15 at 14:31