Python: класс Factory, возвращающий собственных наследников

от автора

Добрый день!

Мы все учились понемногу

Чему-нибудь и как-нибудь,

Так воспитанием, слава богу, У нас немудрено блеснуть….

А.С. Пушкин.

Язык Python для меня не является языком программирования, который я использую в повседневной работе. Для меня более близки ООП языки программирования Java, Object Pascal. Поэтому, не холивара ради, я хочу спросить у сообщества на сколько правильно решение, которое я опишу в данной статье.

Для реализации задач CI/CD проекта был реализован класс работы с репозиториями Mercurial:

repo_types = ('git', 'hg') class Repository:     """     Класс работы с репозиторием системы контроля версий     """     def __init__(self, name: str, directory: str, repo_type = None):         if repo_type is None:             repo_type = 'hg'         if repo_type not in repo_types:             raise Exception("Repository type not supported") ...     def clone(self, branch_name: str = ""):     """     Клонировать репозиторий     """     pass        def commit(self, message: str):     """     Фиксация изменений в локальном репозитории     """     pass     ...

Через некоторое время перед командой остро встал вопрос перехода на Git. Часть репозиториев переходила на Git, часть оставалась в Mercurial. Причем, это нужно было выполнить «еще год назад».

Для оптимизации времени, был использован не отличающийся оригинальность подход:

    def __init__( self, name: str, directory: str, repo_type = None):         if repo_type is None:             repo_type = 'git'         if repo_type not in repo_types:             raise Exception("Repository type not supported")         self.repo_type = repo_type         ...     def merge(self, branch_name: str, merge_revision: str):         """         Слияние ревизий         - branch_name: Название ветки - куда вливать         - merge_revision: Ревизия - что вливать         """         if self.repo_type == 'hg':             ...         else:             ...

Итак, для всех методов класса Repository, были реализовано раздельное поведение для Mercurial и Git. Дополнительно были написаны два класса UnitTest’а — TestRepository_HG и TestRepository_Git, которые покрыли юнит тестами все методы класса Repository. Это позволило безболезненно и, в течении короткого времени, перевести основной репозиторий команды в Git.

Но данный код трудно поддерживать и развивать — он становится техническим долгом. Передо мной встал вопрос: «Как оптимально переписать класс Repository, так, что бы весь остальной код, использующий его, остался без изменений?»

Самый напрашивающийся подход – это паттерн Factory. Для Java, в несколько упрощенном виде, код будет выглядеть примерно так:

public abstract class BaseRepository {     public abstract void clone(String branchName);     public abstract void commit(String message);     ... }  public class HgRepository extends BaseRepository {     @Override     public void clone(String branchName) {...}     @Override     public void commit(String message) {...}     ... }  public class GitRepository extends BaseRepository {     @Override     public void clone(String branchName) {...}     @Override     public void commit(String message) {...}     ... }  public enum RepositoryType {HG, GIT};  public class Repository {     public static BaseRepository createRepository(RepositoryType type) throws Exception {       BaseRepository repository;       switch (type) {           case HG:               repository = new HgRepository();               break;           case GIT:               repository = new GitRepository();               break;           default:               throw new Exception("Repository type not supported ");       }       return repository;     } }

Но данный подход требует четыре файла: базовый класс, реализация Mercurial, реализация для Git и класс фабрика.

Я попробовал в Python объединить класс фабрику и базовый класс. Получился следующий код 🙂

import os from abc import abstractmethod  class Repository:     """     Класс работы с репозиторием системы контроля версий     """     __repo_type_class__: dict = {         "hg": "RepositoryHg.RepositoryHg",         "git": "RepositoryGit.RepositoryGit"     }      @staticmethod     def __get_class__(name: str):         """         Функция получения класса по имени         """         parts = name.split('.')         module = ".".join(parts[:-1])         m = __import__( module )         for comp in parts[1:]:             m = getattr(m, comp)         return m      def __new__(cls, name: str, directory: str, repo_type = None):         """         Создание экземпляра объекта         """         class_name = cls.__repo_type_class__.get(repo_type)         if class_name is None:             raise Exception("Repository type not supported")         repo_class = Repository.__get_class__(class_name)         instance = super().__new__(repo_class)         return instance      def __init__(self, name: str, directory: str, repo_type = None):         """         Инициализация экземпляра объекта         """         self.name = name         self.directory = directory         self.repo_type = repo_type      @abstractmethod     def clone(self, branch_name: str = ""):     """     Клонировать репозиторий     """      @abstractmethod     def commit(self, message: str):     """     Фиксация изменений в локальном репозитории     """     ...
from amtRepository import Repository  class RepositoryHg(Repository):     """     Класс работы с репозиторием Mercurial     """      def __init__(self, name: str, directory: str, repo_type = 'hg'):         """         Инициализация экземпляра объекта Hg         """         super().__init__(name, directory, 'hg')         ...      def clone(self, branch_name: str = ""):     """     Клонировать репозиторий     """     ...      def commit(self, message: str):     """     Фиксация изменений в локальном репозитории     """     ...

Описание класса RepositoryGit я пропускаю, поскольку он интуитивно понятен.

Код, который получился в Python, меня удивил своим подходом. Он отличается от того, как бы я написал на другом языке. В связи с этим я и хотел бы спросить: на сколько это правильное решение?


ссылка на оригинал статьи https://habr.com/ru/post/718952/