-1

Assume we had a User class which should hold references for all different kinds of request, let's say at the moment we only have 2 :RequestA and RequestB, both inherit from the base class Request.

Now we are told to create a class representing the new kind of request in our business. We then create a RequestC class that inherits from Request as well (and this cycle can goes on and on).

How should we design the relationship between User and the different kinds of Requests that our system has?

If we directly couple them (please ignore the poor encapsulation):

public class User
{
    public RequestA RequestA;
    public RequestB RequestB;
    public RequestC RequestC;

    // Foo calls methods from RequestA and RequestB
    public void Foo();
    
    // Boo calls methods from RequestC
    public void Boo();
}

Then, to my understanding, we are forever violating the open/close principle as we will be always modifying our User class when new kinds of Request are created.

Another solution I can think of is to create a collection of Request to hold all the requests there, and make downcasts to RequestA and RequestB inside our Foo method and to RequestC inside Boo and follow this in possible new methods and/or new request classes (assuming the downcast is valid, of course).

I have the feeling that the second approach is not correct either and that there could be a proper way to design such relationship.

That being said, how should we design this relationship (between users and the different types of requests) and keep following all the principles of clean code accepted in the community?

1
  • The problem with questions like these is that context matters. While you seem to be presenting your request types as variations on a polymorphically reusable "base request" type; there are valid cases where explicitly listing these properties makes sense. For example, if these request types were administrative events such as a person's birth certificate and death certificate, it would make a lot of sense to very explicitly define that a person can only have one of each. Context matters, and any answer here will always very much depend on specific contextual clues that are missing here.
    – Flater
    Commented Jul 3, 2021 at 22:32

2 Answers 2

0

Prefer polymorphism to downcasts.

User is violating the Request abstraction by knowing which request it's talking to.

Now that doesn't mean Foo and Boo must use the same Request instance:

public class User
{
    public Request FooRequest;
    public Request BooRequest;

    // Foo calls Request methods on a FooRequest instance
    public void Foo();
    
    // Boo calls Request methods on a BooRequest instance
    public void Boo();
}

What each Request does when it's methods are called can be completely different based on which Request instance is called. That's polymorphism. User doesn't have to know what it's talking to. But it must know Request to know how to talk. User doesn't have to know what it will get.

This means FooRequest and BooRequest will have the same methods that can be called. As defined by Request. But what those methods do once called can be very different (A, B, C, or more). It might seem like User still knows which request it's talking to but it really doesn't. They look the same to User. All it really knows is it's either talking to the 1st Request it was handed or the 2nd. It doesn't know which kind it was handed.

For this to work the Request class must define everything Foo and Boo will call when calling methods on a Request instance. This falls apart if RequestC has new methods that didn't exist before. You can't follow the Open Closed principle if you're adding new methods to the Request interface. Prefer designs that let you instead add new implementations to the existing methods.

If you cant then look into the visitor pattern. It lets you add new methods without breaking old interfaces.

-1

You don’t modify class User when a new Request subclass is created. You modify class User when there is a new business requirement, and that’s when you both can and must modify it.

Is there a reason why the request that seems to be needed has to be implemented as a subclass of RequestB? I’d define an interface for it, so you can call methods of that request based on the interface, and the caller is able to supply anything matching the interface.

4
  • 2
    Under SOLID, particularly the Open-Closed principle, you wouldn't modify User. You'd extend it. Commented Jul 3, 2021 at 15:53
  • Why? When you have no use for a user with two requests only? Clean up old rubbish. Or in ten years time you have a legacy app that nobody can maintain.
    – gnasher729
    Commented Jul 3, 2021 at 18:02
  • It’s not about how many requests the user needs. It’s about User not needing to know anything more then that it’s talking to some Request like thing. If you respect that then User doesn’t change and Request doesn’t change. Something else constructs different Request like things. If you have to, it can also construct a different User like thing. Commented Jul 3, 2021 at 18:08
  • 1
    I think @candied_orange and you are looking at the problem with two different assumptions. If I understood correctly, candied_orange is talking about the case where the design of the User abstraction and the surrounding code is robust enough to play well with OCP (either the design was correct from the start or it was arrived at after several iterations). And you are, if I'm not mistaken, talking about the case where the design of the User class is starting to look like a mismatch, like it's not quite right in light of the new requirements (or a new understanding of the requirements). Commented Jul 3, 2021 at 18:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.