1

I used to have a base object with subtypes behaving in all but the same way -- the difference being in their render methods. This base class defined a default render method, overridden by Some subtypes who have their dependencies wired in.

<?php

    class SubType1 extends BaseType {
        
        function __construct(string $userData) {

            $this->userData = $userData;
        }

        function render (ParserHelper $parser) {

            return $parser->render();
        }
    }

    class SubType2 extends BaseType {
        
        function __construct(string $userData, array $settings) {

            $this->userData = $userData;

            $this->settings = $settings;
        }

        function render (XmlHelper $parser) { // doesn't implement same interface as ParserHelper

            return $parser->getResponse($this->userData);
        }
    }

    class BaseType {

        function render () {

            return "I'm in A";
        }
    }

Keep in mind that the following classes are utilized in this way

    $renderer = $typeFinder->search([ // define critical objects in user facing client. this definition is frequently performed and would be an inconvenience to constantly pull dedicated $parser before definition
        new SubType1("John"),

        new SubType2("Ann", ["dark_mode" => true])
    ]);

Then, internally, where the actual consumption is being done, we had

    $dependencies = $app->autoWire($renderer, "render"); // the violation here

    $renderer->render(...$dependencies);

So, in trying to maintain the same interface, BaseType was made abstract with all the consistent behavior retaining their default positions. The user facing client still looks the same. However, the underlying classes were refactored to

<?php

    class SubType1 extends BaseType {
        
        function __construct(string $userData) {

            $this->userData = $userData;
        }

        function render () {

            return $this->regularParser->render();
        }
    }

    class SubType2 extends BaseType {
        
        function __construct(string $userData, array $settings) {

            $this->userData = $userData;

            $this->settings = $settings;
        }

        function render () {

            return $this->xmlParser->getResponse($this->userData);
        }
    }

    abstract class BaseType {

        abstract function render ();

        function initialize (ParserHelper $parser, XmlHelper $xmlParser):void {

            $this->regularParser = $parser;

            $this->xmlParser = $xmlParser;
        }
    }

Then consumed with

    $renderer->initialize($app->get(ParserHelper::class), $app->get(XmlHelper::class));

    $renderer->render();

Is it ideal to plug in those dependencies to even subtypes who won't be using them? Does this obey the open/closed principle as well? It doesn't look like it to me. The subtypes are safe as far as creating new ones is concerned. But there's the caveat that their parent must receive that parser first. There goes the "closed" part of the principle.

I realize a decorator object who works with the contents of $decorated->userData should do the trick, but I can't wrap my head around how to decouple the decorated user-facing subtype constructors from the decorator; while still allowing decorated autonomy to pick what parser best suits it. MAYBE that client facing subtype just has to swallow the bitter pill of defining the parser each time

I have an idea on how to use the decorator pattern, retain the subtype signature, leave them closed. It looks like this

<?php

    class SubType1 extends BaseType {
        
        function __construct(string $userData) {

            $this->userData = $userData;
        }
    }

    class SubType2 extends BaseType {
        
        function __construct(string $userData, array $settings) {

            $this->userData = $userData;

            $this->settings = $settings;
        }
    }

    abstract class BaseType {

        // define common functionality
    }

    interface Renderable {

        public function render();

        public function setApp();
    }

    class SubType1Decorator extends SubType1 implements Renderable {

        function __construct (string $userData) {

            parent::__construct($userData);
        }

        function render () {

            return $this->app->get(ParserHelper::class)->render();
        }

        public function setApp(App $app) {

            $this->app = $app;
        }
    }

    class SubType2Decorator extends SubType2 implements Renderable {

        function __construct (string $userData, array $settings) {

            parent::__construct($userData, $settings);
        }

        function render () {

            return $this->app->get(XmlHelper::class)->getResponse($this->userData);
        }

        public function setApp(App $app) {

            $this->app = $app;
        }
    }

I don't know if this ticks all the boxes. The definition will be refactored to

    $renderer = $typeFinder->search([
        new SubType1Decorator("John"),

        new SubType2Decorator("Ann", ["dark_mode" => true])
    ]);

Renderer internal consumption:

    $rendererDecorator->setApp($app);

    $rendererDecorator->render();
2

It's less of a violation, but not fully solved yet.

You did correctly fix that the render() method is now properly being overridden instead of adding different signatures, which was an issue in the beginning.

If both your regular parser and your xml parser are part of the base type, and are not just being pushed into the base type because a derived type makes use of it, then your solution (the abstract render() one) is reasonably correct. I would move the initialize logic to an actual constructor, but other than that it's fine.

However, I surmise that this is not the case, based on the classes as presented. Each parser is only used in one (different) subtype.

You shouldn't have this aggregated-subtype-dependencies-initialization logic. Technically, it's an OCP violation rather than an LSP one, but the two are closely related. Workarounds that try to cover for LSP violations commonly lead to OCP violations, and this is what has happened here.

Instead, you should be injecting the parser into the constructor. This way, the derived class instance is able to fully behave as if it's a base class instance (which is what LSP dictates). Base classes and derived classes can have wildly different constructors without violating any good practice principle (in regard to their inheritance, such as LSP), which is why that's the right position to do so.

What I suspect, is that currently the constructor is being called by a consumer which knows what data to pass, but it doesn't have (or should have) the knowledge to choose the correct parser, which is why you're passing in the data via the constructor and not the parser. This is just an inference, correct me if I'm wrong.


The factory pattern

It happens sometimes that some of the constructor arguments are "too complex" for the consumer to inject. In these cases, you need another agent in the system that does it for you.

If you're already using some kind of dependency injection framework, that's usually a good way to go. However, this doesn't quite work when you still want the consumer to supply some of the constructor arguments at will.

When you're trying to mix and match these "simple" (consumer-provided) and "complex" (shouldn't be consumer-provided) arguments, the factory pattern is a good way to go about it.

In essence, the factory still takes in the simple argument (so the consumer can pass them to the factory), but the factory itself is responsible for figuring out the complex arguments. It then injects all of the arguments into the constructor, and hands the initialized object back.

I'm no PHP dev, I hope you'll forgive the C# syntax.

public class SubType1 : BaseType
{
    private readonly string _data;
    private readonly XmlParser _xmlParser;
    
    // Constructor
    public SubType1(string data, XmlParser xmlParser)
    {
        _data = data;
        _xmlParser = xmlParser;
    }

    public override string Render()
    {
        return _xmlParser.GetResponse(_data);
    }
}

public class SubType2 : BaseType
{
    private readonly string _data;
    private readonly JsonParser _jsonParser;
    
    // Constructor
    public SubType1(string data, JsonParser jsonParser)
    {
        _data = data;
        _jsonParser = jsonParser;
    }

    public override string Render()
    {
        return _jsonParser.GetResponse(_data);
    }
}

public static class BaseTypeFactory
{
    public static BaseType CreateSubType1(string data)
    {
        var xmlParser = new XmlParser();

        var result = new SubType1(data, xmlParser);

         return result;
    }

    public static BaseType CreateSubType2(string data)
    {
        var jsonParser = new JsonParser();

        var result = new SubType2(data, jsonParser);

         return result;
    }
}

Note: I've skipped the BaseType because it doesn't really add to what I'm trying to get at, and I feared it would be more of a distraction with me putting it in C# than it would actually add something of value. The main focus of the example is related to the parser objects and the subclass constructors.

Note 2: The static factory was done to keep the example simple. I prefer not using static here for general testing and cleanliness purposes.

Notice how the factory still allows the consumer to pass some of the input, and it in turn fills in the gaps. This gives you the desired behavior in the consumer:

string xmlData = "<name>Foobar</name>";
BaseType myXmlObject = BaseTypeFactory.CreateSubType1(xmlData);

string jsonData = "{ \"name\" : \"Foobar\" }";
BaseType myJsonObject = BaseTypeFactory.CreateSubType2(jsonData);

myXmlObject.Render();
myJsonObject.Render();

Smart factories?

This is just a little extra thought to leave you with.

Factories can be made into smart factories. By that I mean that they could get the freedom to decide for themselves which subtype to return, without the consumer needing to know.

Since you're using an example of subtypes that parse different types of serialized string data, this may be of interest to you. In other words, you could have a factory that takes in a string, figures out whether that string is XML/JSON/... and then returns the correct subtype based on that information. Something along the lines of:

public static class BaseTypeFactory
{
    public static BaseType CreateBaseType(string data)
    {
        if( isXml(data) )
            return new SubType1(data, new XmlParser());
        else if( isJson(data) )
            return new SubType2(data, new JsonParser());
        else
            return new SubType3(data, new RegularParser());
    }

    // Omitted: isXml() and isJson() methods
}

"BaseType" isn't the best name for this, but I stuck with it as you used that name before.

It's not a guarantee that this is what you're after, but I thought I'd mention it as a way of demonstrating how powerful factories can be.

  • The parsers are used in multiple subtypes. Their role in the base class is purely for rendering in subtypes. I defined them there originally, to encourage reuse among subtypes. You are correct about your suggestion to insert the parser through the constructor. That would've been the ideal way to go, but the clients aren't supposed to be concerned with creating parsers. Your paragraph on that (and the next one) is spot on. I'm not sure I like your smart factory owing to how long winded it can potentially get. The regular factories are good, but static methods *coughs – I Want Answers Jan 12 at 17:05
  • What I planned to do is to adopt your factory method, but use dedicated classes rather than statics. However, those factories won't know how to go about manufacturing their parsers. For instance, the XML parser needs to be told where the XML templates are living. This should be injected from the container (or app), who has no access to a self-contained factory. I understand I didn't make that characteristic clear. Other parsers [could] need other details from the app, the only exception being the json parser. How does that work out? – I Want Answers Jan 13 at 3:58
  • @IWantAnswers: The static methods were done to keep things slightly simpler for the usage examples. I wouldn't go the static route myself, tbh, but glossed over it for the sake of clarity on what I was focusing on. – Flater Jan 13 at 9:54
  • @IWantAnswers "This should be injected from the container (or app), who has no access to a self-contained factory." If you have a DI registration in your app, you should be able to also register e.g. a XmlParserConfig object, which the app created and registered itself into the DI framework, meaning it can be distributed by anyone who asks for this config object in their constructor. Note that for factory-constructed classes, the config object has to pass through the factory as the product of the factory isn't connected to the DI framework. – Flater Jan 13 at 9:57
  • @IWantAnswers: The overall point is that the template information comes from somewhere, and it's a matter of connecting that thing to the actual parser - but you'd be doing that in all cases. Asking how a parser can get a config is no different from asking yourself how the consumer is able to get the right kind of factory to generate its parsers. There's no one-size-fits-all answer, but your codebase probably already has a way of linking one class to another, so you should reuse it when applicable. – Flater Jan 13 at 10:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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