Как стать автором
Обновить

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

Время на прочтение4 мин
Количество просмотров3.6K

Добрый день!

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

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

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

А.С. Пушкин.

Язык 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, меня удивил своим подходом. Он отличается от того, как бы я написал на другом языке. В связи с этим я и хотел бы спросить: на сколько это правильное решение?

Теги:
Хабы:
Рейтинг0
Комментарии5

Публикации

Истории

Работа

Python разработчик
118 вакансий
Data Scientist
79 вакансий

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань