Добрый день!
Мы все учились понемногу
Чему-нибудь и как-нибудь,
Так воспитанием, слава богу, У нас немудрено блеснуть….
А.С. Пушкин.
Язык 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/
Добавить комментарий