Wykorzystanie różnych danych wejściowych w metodzie stworzonej na podstawie interfejsu

0

Dzień dobry,
mam odrobinę dziwne pytanie, na które nie za bardzo byłem w stanie znaleźć odpowiedź. Czy poprawne będzie wykorzystanie interfejsu w taki sposób, że przygotowuję w nim deklarację metody o określonej zwrotce oraz z jednym parametrem, a potem w klasach implementujących ten interfejs stworzenie metod, które będą przyjmowały różne typy danych? Jeżeli niepoprawne, to w jaki sposób zrobić to zgodnie ze sztuką?
Taki przykład, o co mi chodzi:

interface Foo 
{
    public function convert($data): ReturnModel;
}

class A implements Foo 
{
    public function convert($json): ReturnModel
    {
        // obrabiamy jsona
        return new ReturnModel();
}

class B implements Foo
{
    public function convert($model): ReturnModel
    {
        // obrabiamy sobie dane na podstawie modelu
        return new ReturnModel();
    }
}
1

Jest to semi-prawidłowe: na pewno lepsze od braku interfejsu w ogóle, lecz starałbym się mimo wszystko unikać interfejsów o niejasnych typach wejściowych / wyjściowych, ponieważ wymagają one pomijania adnotacji typów w miejscu ich wywołań (jako że $data może być technicznie dowolnego typu), przez co problematyczne staje się śledzenie co jest czym w którym miejscu programu.

Co powiesz na taki interfejs?

interface Foo {
    public function getModel(): ReturnModel;
}

class A implements Foo {
    private $model;

    public function __construct($json) {
        $this->model = $json->magic(); // ew. `$this->json = $json;` z konwersją w `getModel()`
    }

    public function getModel(): ReturnModel {
        return $this->model;
    }
}

class B implements Foo {
    private $model;

    public function __construct($model) {
        $this->model = $model->magic(); // ew. `$this->model = $model;` z konwersją w `getModel()`
    }

    public function getModel(): ReturnModel {
        return $this->model;
    }
}

Ma on dodatkową zaletę polegającą na tym, że łatwo go zmienić w builder pattern umożliwiający konfigurację tego, jak zwracany będzie ReturnModel:

class C implements Foo {
    private $input;
    private $strategy;

    public function __construct($input) {
        $this->input = $input;
        $this->strategy = new DefaultStrategy();
    }

    public function withStrategy(...) {
        $this->strategy = $strategy;
        return $this;
    }

    public function getModel(): ReturnModel {
        $input = $this->strategy->doSomething($input):
        return $input->magic();
    }
}
0

Dzięki za odpowiedź!
Zastanawiałem się nad takim rozwiązaniem, jakie proponujesz, ale nie jest ono do końca satysfakcjonujące, bo ten sposób nie gwarantuje, że wszystkie klasy implementujące ten interfejs będą wymagały podania danych wejściowych w konstruktorze.

0

ten sposób nie gwarantuje, że wszystkie klasy implementujące ten interfejs będą wymagały podania danych wejściowych w konstruktorze

Dlaczego chciałbyś, aby było to wymagane?

0

Automat, który musi wygenerować instancje klasy na podstawie danych wejściowych. Typ danych wejściowych zależny od tego, co wybierze sobie user. Raz źródłem danych jest json, raz inny model, raz coś jeszcze innego. Chciałbym żeby konwertery miały stałą strukturę in i out, a przekazywanie tego w konstruktorze mi nie zagwarantuje, że każdy konwerter będzie miał obowiązkowy parametr w konstruktorze.

0

Z drugiej strony, tworząc ujednolicony interfejs tracisz możliwość określania typów wejściowych, co z kolei może sprawić wrażenie jakoby każdy konwerter był w stanie przyjąć dowolny typ (a nie ten jeden konkretny, do którego został stworzony).

Ciekawostka: Twój oryginalny interfejs da się prawidłowo odwzorować w Ruście, jako że ichniejsze traity mogą być dodatkowo parametryzowane po typie:

trait Converter {
  type Input;

  fn convert(&self, value: Self::Input) -> ReturnModel;
}

impl Converter for FooConverter {
  type Input = Foo;

  fn convert(&self, value: Foo) -> ReturnModel {
    todo!()
  }
}

impl Converter for BarConverter {
  type Input = Bar;

  fn convert(&self, value: Bar) -> ReturnModel {
    todo!()
  }
}

Niestety w przypadku stosunkowo biednego systemu typów PHPa, najlepszym wyjściem (zakładając, że chciałbyś pozostać przy oryginalnym projekcie, który IMO jest wadliwy) jest pozbycie się adnotacji typów.

1 użytkowników online, w tym zalogowanych: 0, gości: 1