LINUX.ORG.RU

Как правильно использовать полиморфизм на моделях (из MVC)?

 , , ,


0

1

Ситуация такова:
Есть класс MyApp::Model::Repository. Этот класс является потомком DBIx::Class::Core, т.е. ORM отображением сущности из БД.
Среди атрибутов сущности Repository:

  • name - произвольное имя (для идентификации)
  • uri - URI адрес репозитория
  • vcs - собственно название Version Control System, под управлением которой находится данный репозиторий (напр. 'git')

Задача:
Нужно реализовать метод list_files для Model::Repository. В данной ситуации так и напрашивается решение использовать полиморфизм с наследованием. Но вот вопрос — как это сделать правильно?

★★★★★

Последнее исправление: KennyMinigun (всего исправлений: 1)

Ответ на: комментарий от gh0stwizard

Так а что делать с методом list_files, как в нём определить кто должен файлы листить: git или svn?

Т.е.:

package MyApp::Model::Repository;

sub list_files {
    my ($self, $repo) = @_;

    my $vcs = $repo->vcs; # тут может быть что угодно: git, hg, ...

    # а тут надо получить список файлов из репозитория
    # git -> `git ls-files`
    # hg -> `hg locate`
    # svn -> `svn list`
}

Но вообще желательно, чтоб метод list_files проксировался из соответствующего хелпера: MyApp::*::Git, MyApp::*::Mercurial, ... В зависимости от $repo->vcs.

Кроме list_files будут еще контрактные методы, внутренняя логика которых тоже зависит от типа VCS.

P.S. Кажется мне, что это неправильный подход и я где-то перемудрил

KennyMinigun ★★★★★
() автор топика
Последнее исправление: KennyMinigun (всего исправлений: 3)
Ответ на: комментарий от KennyMinigun

Жесть. Будь проще, множественное наследование в перле не такое навороченное как ты полагаешь. Наследование идет от первого указанного класса/модуля, а все остальные классы лишь склады методов, которых нет в родителе или потомке. Для git/svn/cvs создаешь новую пару родитель - потомки и передаешь или инкапсулируешь в Model::Repository

gh0stwizard ★★★★★
()
Ответ на: комментарий от gh0stwizard

Та я и не планировал множественное наследование.

Сделал чуть проще. Концепция такая:

package MyApp::Model::Repository;

my %providers = (
    'git' => 'MyApp::VCS::Git',
    'hg' => 'MyApp::VCS::Mercurial',
);

sub list_files {
    my ($self, $repo) = @_;

    my $vcs = $providers{$repo->vcs}->new();

    return $vcs->list_files($repo);
}

Ну и плюс все классы MyApp::VCS::* используют роль MyApp::VCS, в которой прописан контракт.

// Конечно в реальном коде всё чуть посложнее и используется Module::Pluggable;

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от KennyMinigun

Вот другая техника :)

package MyApp::Model::Repository;

use MyApp::VCS;

my %providers = (
    'git' => 'MyApp::VCS::Git',
    'hg' => 'MyApp::VCS::Mercurial',
);

sub list_files {
    my ($self, $repo) = @_;

    my $vcs = MyApp::VCS->new();

    if (my $mod = $providers{$repo->vcs}) {
      eval "require $mod"; # perldoc require :)
      $vcs = bless $vcs, $mod; # convert object to new class
    }

    return $vcs->list_files($repo);
}

Не знаю насколько это тебе пригодится.

gh0stwizard ★★★★★
()

В данной ситуации так и напрашивается решение использовать полиморфизм с наследованием.

В данной ситуации напрашивается решение вынести list_files в отдельный класс (т.н. стратегия, или прокси):

interface AnyVCS {
    Context open_repository(url,user,passwd);
    string [] list_files();
}

public class Mercurial : AnyVCS {
    public Mercurial() { ... }
    Context open_repository(url,user,passwd) { ... }
    string [] list_files(Context ctx) { ... }
}

public class Git : AnyVCS {
    public Git() { ... }
    Context open_repository(url,user,passwd);
    string [] list_files(Context ctx) { ... }
}

class Repository {
    string name;
    VCS vcs;
    Context ctx;
    public Repository(name,vcs) {
        this.name = name;
        this.vcs = vcs;
    }
    public void open(url,user,passwd) { ctx = vcs.open_repository(url,user,passwd); }
    public string [] list_files() { return vcs.list_files(cxt); }
}

Repository r = new Repository("Sample1",new Git(...));
Repository r = new Repository("Sample1",new Mercurial(...));

В результате добавление новой VCS сводится к реализации одного класса, который будет нести всю логику работы с этой VCS, а «модельные» классы (Repository, File, Branch, Version и т.п.) править уже не нужно.

no-dashi ★★★★★
()
Последнее исправление: no-dashi (всего исправлений: 1)
Ответ на: комментарий от no-dashi

Да, я так почти изначально и планировал. (В принципе так и сделал). Главная проблема была в том, как порождать нужную сущность. Т.е. требовалось создавать MyApp::VCS::Git, если тип репозитория 'git', причём не хардкодить это.

Сейчас у меня подклассы MyApp::VCS::* регистрируются как плагины MyApp::VCS. А сама MyApp::VCS является ролью (интерфейс + некоторые методы), которую должны имплементировать потомки. Т.е. VCS::serve() создает экземпляр нужного потомка-плагина.

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от KennyMinigun

Ну в общем-то без reflection и introspection тебе всяко придётся что-нибудь «хардкодить», как минимум регистрацию класса в фабрике. Иное дело, что этот «хардкод» в этом случае до минимума сводится.

no-dashi ★★★★★
()
Ответ на: комментарий от no-dashi

Ну в общем-то без reflection и introspection тебе всяко придётся что-нибудь «хардкодить»,

В перле это попроще делается :)

package MyApp::VCS;

use Moose::Role;
use Moose::Util qw(does_role);

use Module::Pluggable
    max_depth => 1,
    search_path => [ __PACKAGE__ ]
;

# в этом массиве будут имена всех классов в неймспейсе MyApp::VCS::*
# которые имплементируют роль MyApp::VCS
my @successors = grep { does_role($_, __PACKAGE__) } __PACKAGE__->plugins;

Ну.. да, по сути это reflection.

KennyMinigun ★★★★★
() автор топика
Последнее исправление: KennyMinigun (всего исправлений: 2)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.