Choosing between DI (without container) and Composition

I’m working on a classes that are designed to read a defined file format; said format is comprised of 2 separate files (FileA and FileB). For ease of use, the format is referred to as the FileA format, but a valid “file” must have a FileB. The files share the same path prefix, but alter in suffix; therefore my current classes appears as:

class FileReaderA:
    def __init__(self, path: pathlib.Path):
            self.a = path.with_suffix('.fa')
            self.b = FileReaderB(path)
    # methods related to operating on FileA types

class FileReaderB:
    def __init__(self, path: pathlib.Path):
            self.b = path.with_suffix('.fa')
    # methods related to operating on FileB types 

The classes are separated since FileReaderB does not depend on FileReaderA and can be used independently to read FileB types, however a valid FileA requires its associated FileB. Therefore, composition made sense, at first. Now I’m wondering if DI is the more appropriate solution, but it requires that FileReaderB be explicitly instantiated and passed to the constructor of FileReaderA, which is less than ideal form a user standpoint with this API:

>>> frb = FileReaderB(path)
>>> fra = FileReaderA(path, frb)

Thus I am forced to abstract it by using a factory:

def reader(path: pathlib.Path):
    return FileReaderA(path, FileReaderB(path))


>>> fra = reader(path)

This seems verbose and I feel as though I’m introducing more code and layers of abstraction to achieve the same result, all around the guise for better unit testing of decoupled classes.

What is wrong with just using Composition over enforcing DI?

Go to Source
Author: pasta_sauce

How to visualise Inheritance and Polymorphism?

I want to make diagrams that explain how Inheritance and Polymorphism work conceptually.

Assume that we have a Cat class that inherits from the Animal class. The way that I can think of a Cat object is that it is an object that contains a copy of the members of the Cat class and also it have an Animal object inside of it.

Note: technically, I think that all Cat objects share the same copy of the methods of the Cat class, however, it is clearer to conceptually think of every Cat object as if it contains its own copy of the methods of the Cat class.

Now when we do:

Cat cat1 = new Cat();

Then cat1 will point to the Cat object, this is how the diagram would look like:

enter image description here

The operations that are allowed on the cat1 variable are the following:

  • cat1.x (will access the x variable inside the Cat object).

  • cat1.move() (will call the move() method inside the Cat object).

  • cat1.jump() (will call the jump() method inside the Animal object).

And when we do:

Animal animal1 = cat1;

Then animal1 will point to the Animal object that is inside the Cat object, this is how the diagram would look like:

enter image description here

Note: the arrow pointing at the Animal object doesn’t mean that animal1 actually point to a different location in memory than cat1, the truth is that animal1 and cat1 point to the same location in memory, the arrow pointing at the Animal object is just to indicate that animal1 can only access the members inside the Animal object (unless we have an overridden method).

The operations that are allowed on the animal1 variable are the following:

  • animal1.x (will access the x variable inside the Animal object).

  • animal1.move() (now if the move() method is not overridden in the Cat class, then animal1.move() should call the move() method inside the Animal object, however, since the move() method is overridden in the Cat class, then animal1.move() would call the move() method inside the Cat object).

  • animal1.jump() (will call the jump() method inside the Animal object).

Is my understanding correct?

Go to Source
Author: Tom

Decoupling and Command Pattern

I am working on some project to learn how to make bigger and better software (multiplayer game) and I found a problem about having in my code a design pattern Command Pattern and anti-pattern God Object. Often I do end up win the latter one and I know that Fascades are okay, but my understanding of something being right and wrong in OOP is very blurry.

I’ve implemented the Command Pattern. Shortly, my command:

public interface IGameCommand : ICommand
{
    bool Execute(Game game);
}

And an executor

public interface IExecutor<TState, TCommand>
{
    void Execute(TCommand command);
}

Let’s say that I have a command that does a lot: modifies some data, plays sound etc.

So, in my case, this should look like this:

public class MagicSpell: IGameCommand
{
    int x; int y; int damage; string soundClipName; string effectName;
    bool Execute(Game game)
    {
         game.gameState.map[x][y].unit.TakeDamage(damage);
         ...
         game.soundPlayer.PlaySound(soundClipName);
         ...
         game.specialEffectPlayer.PlayEffect(effectName);
         ...
    }
}

As you can see, this forces my Game class to become a God object… or doesn’t it? Since my Game class contains specialized classes that do their thing I am fulfilling the Single responsibility principle. I have some bad experience with ending up with a God Object, but is my solution viable and acceptable with OOP?

Or maybe is something wrong with my Command Pattern implementation and Execute(Game game)?

Maybe making some specialized commands would help?

Go to Source
Author: Clockworker

Can a GoF Builder implementation be considered valid without an explicit getProduct()?

For studying purpose, I’ve tried to create a simple PHP implementation of Builder GoF (not the Joshua Bloch’s one) inspired on this slide.

The main goal of this example is to encapsulate the construction of data files from diferents inputs (Directors) and outputs (Builders)

Lots of Builder example (including the wikipedia’s one) define an explicit “getProduct/getResult” on the Builder Class.

In my case, I thought the creation of output file would be considered the literal product, so I have opted to not use a method to get the file.

So, I would like to know if the absence of this method would invalidate it as Builder GoF implementation.

<?php
namespace BuilderGoFEnglish;
abstract class Builder {
    protected SplFileObject $output;
    public function __construct(string $outputFileName) {
        $this->output = new SplFileObject($outputFileName, 'w');
    }
    abstract function addHeader(array $header);
    abstract function addLine(array $linha);
    abstract function finish();
}
abstract class Director {
    protected Builder $builder;
    public function __construct(Builder $builder) {
        $this->builder = $builder;
    }
    public abstract function build(string $inputFileName);
}
class XmlDirector extends Director {
    public function build(string $inputFileName) {
        $document = new DOMDocument();
        $document->preserveWhiteSpace = false;
        $document->load($inputFileName);
        $root = $document->firstChild;
        $item1 = iterator_to_array($root->firstChild->childNodes);
        $this->builder->addHeader(array_column($item1, 'tagName'));
        foreach($root->childNodes as $child){
            $item = iterator_to_array($child->childNodes);
            $this->builder->addLine(array_column($item1, 'nodeValue'));
        }
        $this->builder->finish();
    }
}
class JsonDirector extends Director {
    public function build(string $inputFileName) {
        $jsonArray = json_decode(file_get_contents($inputFileName));
        $this->builder->addHeader(array_keys((array) $jsonArray[0]));
        foreach ($jsonArray as $jsonObject) {
            $this->builder->addLine((array) $jsonObject);
        }
        $this->builder->finish();
    }
}
class HtmlBuilder extends Builder {
    private DOMDocument $document;
    private DOMElement $table;
    public function __construct(string $output) {
        parent::__construct($output);
        $this->document = new DOMDocument('1.0', 'utf-8');
        $this->document->appendChild($this->document->createElement('html'));
        $this->table = $this->document->createElement('table');
        $this->table->setAttribute('border', 1);
        $this->document->firstChild->appendChild($this->table);
    }
    private function createTableRow(array $linha, $tipo = 'td'){
        $tr = $this->document->createElement('tr');
        array_map(fn($v) =>
                $tr->appendChild($this->document->createElement($tipo, $v)),
                $linha);
        $this->table->appendChild($tr);
    }
    public function addHeader(array $header) {
        $this->createTableRow($header, 'th');
    }
    public function addLine(array $linha) {
        $this->createTableRow($linha);
    }
    public function finish() {
        $this->output->fwrite($this->document->saveHTML());
    }
}
class CsvBuilder extends Builder {
    private array $csvArray = [];
    public function addHeader(array $header) {
        $this->csvArray[] = $header;
    }
    public function addLine(array $linha) {
        $this->csvArray[] = $linha;
    }
    public function finish() {
        foreach ($this->csvArray as $linha) {
            $this->output->fputcsv($linha);
        }
    }
}

Go to Source
Author: celsowm