Альтернативное описание паттернов проектирования

в 4:50, , рубрики: python, образование, паттерны проектирования, метки: , ,

В настоящие время, кроме знаменитой книги Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. Приемы объектно-ориентированного проектирования. Паттерны проектирования, существует много различных источников, как по самим паттернам проектирования, так и по примерам их применения, да еще на разных языках программирования. С учетом этого и того, что во многих случаях, очень сложно различить один паттерн проектирования от другого, поскольку не всегда имеется его четкое, формальное определение, сводит в результате все положительные моменты от применения паттернов проектирования к нулю. Особенно это заметно на часто прилагаемой к примерам диаграммах на UML, поскольку они получаются малоинформативными и как бы ни о чем.

Критерии «идеального» решения

  • Выбрать пример, на котором можно продемонстрировать большинство паттернов проектирования.
  • Постановка задачи для примера должна быть простой, а предметная область примера всем знакомой.
  • Не давать определение паттерна, раз его сложно формализовать, а показать его отличие от другого на разных решениях (паттерны ведь разные) для одной предметной области.

Решение

Модель файловой системы с папками, файлами и связями, возможностью просмотра и поиска. Для упрощения модели, в файловой системе нет возможности удалять элементы и также нет проверки на дублирующие имена в папке.

В качестве языка программирования использовать Python. Python дает компактный, читаемый код и обладает динамической типизацией, иначе объявление типов+приведение типов приведет, как к увеличению кода, так и к его захламлению. Я пробовал рассматривать в качестве альтернативы функциональный язык (haskell), а в качестве объектно-ориентированного со статической типизацией C++. Код получается в полтора, два раза длиннее и гораздо хуже читается. Особенно это касается различий при сравнении разных паттернов. Но замечу, переписать код конкретного паттерна с Python на Haskell или C++ лично мне не составляло большого труда.

Я не буду давать описание паттернов, поскольку по их названию легко найти о них информацию в сети. Это также связано с тем, что иногда предлагаемое мною решение будет отличатся от «стандартного».

В топике по приведенной ниже схеме построены примеры для:

  • Behavioral: interpreter, iterator;
  • Creational: abstractfactory, singleton;
  • Structural: facade.

Базовая реализация модели файловой системы

Базовый пример

# -*- coding: utf-8 -*-

fileSystem = None

class File:
  def __init__(self, size):
    self.size = size
    self.ind = fileSystem.create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind)

class Dir:
  def __init__(self):
    self.ind = fileSystem.create(self)
    self.parent = None
    self.container = {}

  def getName(self, ind):
    return self.name() + self.container[ind]

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind) + "/"

  def size(self):
    return len(self.container)

  def add(self, name, file):
    file.parent = self
    self.container[file.ind] = name

class Root(Dir):
  def __init__(self):
    Dir.__init__(self)

  def name(self):
    return "/"

class Link:
  def __init__(self, link):
    self.link = link
    self.ind = fileSystem.create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return " -> " + self.link.name()
    else:
      return self.parent.getName(self.ind) + " -> " + self.link.name()

  def size(self):
    return 8

class FileSystem:
  def __init__(self):
    self.container = {}

  def create(self, file):
    ind = hash(file)
    self.container[ind] = file
    return ind

  def find(self, name):
    for value in self.container.itervalues():
      if value.name() == name:
        return value
    return None

  def printAll(self):
    for value in self.container.itervalues():
      if isinstance(value, File):
        print "file: %8d %s" % (value.size, value.name())
      elif isinstance(value, Dir):
        print " dir: %8d %s" % (value.size(), value.name())
      elif isinstance(value, Link):
        print "link: %8d %s" % (value.size(), value.name())

if __name__ == "__main__":
  fileSystem = FileSystem()
  root = Root()
  etc = Dir()
  root.add("etc", etc)
  home = Dir()
  root.add("home", home)
  user = Dir()
  home.add("user", user)
  user.add("readme.txt", File(1177))
  user.add(".etc", Link(etc))
  fileSystem.printAll()
  print "find('/home/user')=", fileSystem.find("/home/user")
  print "find('/home/user/')=", fileSystem.find("/home/user/")

Краткое описание кода.
fileSystem — глобальная переменная задающая файловою систему
class File — файл, который имеет размер, уникальный ключ для файловой системы и владельца (директорию). Директория по уникальному ключу определяет имя файла.
class Dir — директория (папка) представляет собой контейнер, который имеет размер равный числу элементов контейнера, уникальный ключ и владельца (директорию). Аналогично владелец (другая директория) по уникальному ключу определяет имя директории. К директории, как к контейнеру, можно добавить элемент с его уникальным ключом и именем.
class Root(Dir) — выделенная корневая директория
class Link — линк (связь) задает горизонтальные связи в иерархической файловой системе. Имеет фиксированный размер, уникальный ключ, владельца (директорию) и имя состоящие из имени линка и имени директории или файла или линка, на которое этот линк ссылается.
class FileSystem — файловая система, представляющая собой контейнер для директорий, файлов и линков с возможностью их просмотра по их уникальным ключам и поиску по имени.

В результате выполнения кода имеем следующий результат:

 dir:        0 /etc/
 dir:        1 /home/
 dir:        2 /home/user/                                                                                                                                    
file:     1177 /home/user/readme.txt                                                                                                                          
link:        8 /home/user/.etc -> /etc/                                                                                                                       
 dir:        2 /                                                                                                                                              
find('/home/user')= None                                                                                                                                      
find('/home/user/')= <__main__.Dir instance at 0x7f6f4664f6c8>                                                                                                

Следующий раздел, естественно, как «Hello World!» для языков программирования, будет посвящен singleton.

Singleton

Singleton — гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа. Существенно то, что можно пользоваться именно экземпляром класса, так как при этом во многих случаях становится доступной более широкая функциональность. Например, к описанным компонентам класса можно обращаться через интерфейс, если такая возможность поддерживается языком.

На нашем, осмысленном примере это означает, что файловая система может быть только одна. Этот факт в базовом примере представлен глобальной переменной fileSystem, но пользователь может это значение заменить на другое. Чтобы застраховать себя от этого сделана замена в местах использования fileSystem на вызов конструктора FileSystem() класса FileSystem.

Для удобства сравнения кода с базовым примером, он слева, добавлена картинка аля — diff.

Изображение - savepic.su — сервис хранения изображений

Код примера для Singleton

# -*- coding: utf-8 -*-

class File:
  def __init__(self, size):
    self.size = size
    self.ind = FileSystem().create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind)

class Dir:
  def __init__(self):
    self.ind = FileSystem().create(self)
    self.parent = None
    self.container = {}

  def getName(self, ind):
    return self.name() + self.container[ind]

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind) + "/"

  def size(self):
    return len(self.container)

  def add(self, name, file):
    file.parent = self
    self.container[file.ind] = name

class Root(Dir):
  def __init__(self):
    Dir.__init__(self)

  def name(self):
    return "/"

class Link:
  def __init__(self, link):
    self.link = link
    self.ind = FileSystem().create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return " -> " + self.link.name()
    else:
      return self.parent.getName(self.ind) + " -> " + self.link.name()

  def size(self):
    return 8

class FileSystem:
  dict = {'container': {}}

  def __init__(self):
    self.__dict__ = FileSystem.dict

  def create(self, file):
    ind = hash(file)
    self.container[ind] = file
    return ind

  def find(self, name):
    for value in self.container.itervalues():
      if value.name() == name:
        return value
    return None

  def printAll(self):
    for value in self.container.itervalues():
      if isinstance(value, File):
        print "file: %8d %s" % (value.size, value.name())
      elif isinstance(value, Dir):
        print " dir: %8d %s" % (value.size(), value.name())
      elif isinstance(value, Link):
        print "link: %8d %s" % (value.size(), value.name())

if __name__ == "__main__":
  root = Root()
  etc = Dir()
  root.add("etc", etc)
  home = Dir()
  root.add("home", home)
  user = Dir()
  home.add("user", user)
  user.add("readme.txt", File(1177))
  user.add(".etc", Link(etc))
  FileSystem().printAll()
  print "find('/home/user')=", FileSystem().find("/home/user")
  print "find('/home/user/')=", FileSystem().find("/home/user/")

Тут использован чисто Python-овский трюк с заменой у каждого экземпляра класса его словаря self.__dict__ на общий словарь FileSystem.dict для всех экземпляров класса FileSystem

В результате выполнения кода имеем повтор результата базового примера:

 dir:        0 /etc/
 dir:        1 /home/
 dir:        2 /home/user/                                                                                                                                    
file:     1177 /home/user/readme.txt                                                                                                                          
link:        8 /home/user/.etc -> /etc/                                                                                                                       
 dir:        2 /                                                                                                                                              
find('/home/user')= None                                                                                                                                      
find('/home/user/')= <__main__.Dir instance at 0x7f6a37381638>                                                                                                

Abstractfactory

Abstract factory — предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов.

На нашем, осмысленном примере это означает, класс FileSystemAbstract представляет собой интерфейс для создания головной папки, папок, файлов и ссылок. В результате это позволяет изменять поведение системы порождая от FileSystemAbstract новые классы, например FileSystem.

Изображение - savepic.su — сервис хранения изображений

Код примера для Abstractfactory

# -*- coding: utf-8 -*-

fileSystem = None

class File:
  def __init__(self, size):
    self.size = size
    self.ind = fileSystem.create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind)

class Dir:
  def __init__(self):
    self.ind = fileSystem.create(self)
    self.parent = None
    self.container = {}

  def getName(self, ind):
    return self.name() + self.container[ind]

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind) + "/"

  def size(self):
    return len(self.container)

  def add(self, name, file):
    file.parent = self
    self.container[file.ind] = name

class Root(Dir):
  def __init__(self):
    Dir.__init__(self)

  def name(self):
    return "/"

class Link:
  def __init__(self, link):
    self.link = link
    self.ind = fileSystem.create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return " -> " + self.link.name()
    else:
      return self.parent.getName(self.ind) + " -> " + self.link.name()

  def size(self):
    return 8

class FileSystemAbstract:
  def __init__(self):
    self.container = {}

  def create(self, file):
    ind = hash(file)
    self.container[ind] = file
    return ind

  def createFile(self, size):
    pass

  def createDir(self):
    pass

  def createRoot(self):
    pass

  def createLink(self, link):
    pass

  def find(self, name):
    for value in self.container.itervalues():
      if value.name() == name:
        return value
    return None

  def printAll(self):
    for value in self.container.itervalues():
      if isinstance(value, File):
        print "file: %8d %s" % (value.size, value.name())
      elif isinstance(value, Dir):
        print " dir: %8d %s" % (value.size(), value.name())
      elif isinstance(value, Link):
        print "link: %8d %s" % (value.size(), value.name())

class FileSystem(FileSystemAbstract):
  def __init__(self):
    FileSystemAbstract.__init__(self)

  def createFile(self, size):
    return File(size)

  def createDir(self):
    return Dir()

  def createRoot(self):
    return Root()

  def createLink(self, link):
    return Link(link)

if __name__ == "__main__":
  fileSystem = FileSystem()
  root = fileSystem.createRoot()
  etc = fileSystem.createDir()
  root.add("etc", etc)
  home = fileSystem.createDir()
  root.add("home", home)
  user = fileSystem.createDir()
  home.add("user", user)
  user.add("readme.txt", fileSystem.createFile(1177))
  user.add(".etc", fileSystem.createLink(etc))
  fileSystem.printAll()
  print "find('/home/user')=", fileSystem.find("/home/user")
  print "find('/home/user/')=", fileSystem.find("/home/user/")

В результате выполнения кода имеем повтор результата базового примера с точностью до перестановки строк:

file:     1177 /home/user/readme.txt
link:        8 /home/user/.etc -> /etc/
 dir:        2 /
 dir:        0 /etc/
 dir:        1 /home/
 dir:        2 /home/user/
find('/home/user')= None
find('/home/user/')= <__main__.Dir instance at 0x7f32ea5b1bd8>

Iiterator

Iterator — представляет собой объект, позволяющий получить последовательный доступ к элементам объекта-агрегата без использования описаний каждого из объектов, входящий в состав агрегации.

На нашем, осмысленном примере это означает, что мы вместо специализированной функции printAll класса FileSystem, которая распечатывала содержимое файловой системы, подставим более универсальную iteritems, возвращающую итератор.

Изображение - savepic.su — сервис хранения изображений

Код примера для Iiterator

# -*- coding: utf-8 -*-

fileSystem = None

class File:
  def __init__(self, size):
    self.size = size
    self.ind = fileSystem.create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind)

class Dir:
  def __init__(self):
    self.ind = fileSystem.create(self)
    self.parent = None
    self.container = {}

  def getName(self, ind):
    return self.name() + self.container[ind]

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind) + "/"

  def size(self):
    return len(self.container)

  def add(self, name, file):
    file.parent = self
    self.container[file.ind] = name

class Root(Dir):
  def __init__(self):
    Dir.__init__(self)

  def name(self):
    return "/"

class Link:
  def __init__(self, link):
    self.link = link
    self.ind = fileSystem.create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return " -> " + self.link.name()
    else:
      return self.parent.getName(self.ind) + " -> " + self.link.name()

  def size(self):
    return 8

class FileSystem:
  def __init__(self):
    self.container = {}

  def create(self, file):
    ind = hash(file)
    self.container[ind] = file
    return ind

  def find(self, name):
    for value in self.container.itervalues():
      if value.name() == name:
        return value
    return None

  def iteritems(self):
    return self.container.itervalues()

if __name__ == "__main__":
  fileSystem = FileSystem()
  root = Root()
  etc = Dir()
  root.add("etc", etc)
  home = Dir()
  root.add("home", home)
  user = Dir()
  home.add("user", user)
  user.add("readme.txt", File(1177))
  user.add(".etc", Link(etc))
  for value in fileSystem.iteritems():
    if isinstance(value, File):
      print "file: %8d %s" % (value.size, value.name())
    elif isinstance(value, Dir):
      print " dir: %8d %s" % (value.size(), value.name())
    elif isinstance(value, Link):
      print "link: %8d %s" % (value.size(), value.name())
  print "find('/home/user')=", fileSystem.find("/home/user")
  print "find('/home/user/')=", fileSystem.find("/home/user/")

В результате выполнения кода имеем повтор результата базового примера с точностью до перестановки строк вывода:

 dir:        2 /home/user/
file:     1177 /home/user/readme.txt
link:        8 /home/user/.etc -> /etc/
 dir:        2 /
 dir:        0 /etc/
 dir:        1 /home/
find('/home/user')= None
find('/home/user/')= <__main__.Dir instance at 0x7f74ee8c3638>

Interpreter

Interpreter — решает часто встречающуюся, но подверженную изменениям, задачу.

На нашем, осмысленном примере это означает, что функцию поиска find класса FileSystem желательно обобщить на возможность поиска всех путей, куда искомая строка входит в качестве подстроки, а также на возможность работы с масками. Для этого можно воспользоватся регулярными выражениями и стандартным модулем re языка Python для работы с ними.

Изображение - savepic.su — сервис хранения изображений

Код примера для Interpreter

# -*- coding: utf-8 -*-

import re

fileSystem = None

class File:
  def __init__(self, size):
    self.size = size
    self.ind = fileSystem.create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind)

class Dir:
  def __init__(self):
    self.ind = fileSystem.create(self)
    self.parent = None
    self.container = {}

  def getName(self, ind):
    return self.name() + self.container[ind]

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind) + "/"

  def size(self):
    return len(self.container)

  def add(self, name, file):
    file.parent = self
    self.container[file.ind] = name

class Root(Dir):
  def __init__(self):
    Dir.__init__(self)

  def name(self):
    return "/"

class Link:
  def __init__(self, link):
    self.link = link
    self.ind = fileSystem.create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return " -> " + self.link.name()
    else:
      return self.parent.getName(self.ind) + " -> " + self.link.name()

  def size(self):
    return 8

class FileSystem:
  def __init__(self):
    self.container = {}

  def create(self, file):
    ind = hash(file)
    self.container[ind] = file
    return ind

  def find(self, name):
    pattern = re.compile(name)
    r = []
    for value in self.container.itervalues():
      if pattern.search(value.name()):
        r.append(value.name())
    return r

  def printAll(self):
    for value in self.container.itervalues():
      if isinstance(value, File):
        print "file: %8d %s" % (value.size, value.name())
      elif isinstance(value, Dir):
        print " dir: %8d %s" % (value.size(), value.name())
      elif isinstance(value, Link):
        print "link: %8d %s" % (value.size(), value.name())

if __name__ == "__main__":
  fileSystem = FileSystem()
  root = Root()
  etc = Dir()
  root.add("etc", etc)
  home = Dir()
  root.add("home", home)
  user = Dir()
  home.add("user", user)
  user.add("readme.txt", File(1177))
  user.add(".etc", Link(etc))
  fileSystem.printAll()
  print "find('/home/user')=", fileSystem.find("/home/user")
  print "find(r'.*.txt$')=", fileSystem.find(r".*.txt$")

В результате выполнения кода имеем некоторый повтор результата базового примера, а также совершенно другую работу функции поиска find. Они теперь ищет не точное совпадение, а все подстроки и умеет работать с масками заданными регулярными выражениями:

 dir:        2 /home/user/
file:     1177 /home/user/readme.txt
link:        8 /home/user/.etc -> /etc/
 dir:        2 /
 dir:        0 /etc/
 dir:        1 /home/
find('/home/user')= ['/home/user/', '/home/user/readme.txt', '/home/user/.etc -> /etc/']
find(r'.*.txt$')= ['/home/user/readme.txt']

Facade

Facade — позволяет скрыть сложность системы путем сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы.

На нашем, осмысленном примере это означает, что строим класс FileFacade в котором инкапсулируем все возможности работы с нашей файловой системой.

Изображение - savepic.su — сервис хранения изображений

Код примера для Facade

# -*- coding: utf-8 -*-

fileSystem = None

class File:
  def __init__(self, size):
    self.size = size
    self.ind = fileSystem.create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind)

class Dir:
  def __init__(self):
    self.ind = fileSystem.create(self)
    self.parent = None
    self.container = {}

  def getName(self, ind):
    return self.name() + self.container[ind]

  def name(self):
    if not self.parent:
      return ""
    else:
      return self.parent.getName(self.ind) + "/"

  def size(self):
    return len(self.container)

  def add(self, name, file):
    file.parent = self
    self.container[file.ind] = name

class Root(Dir):
  def __init__(self):
    Dir.__init__(self)

  def name(self):
    return "/"

class Link:
  def __init__(self, link):
    self.link = link
    self.ind = fileSystem.create(self)
    self.parent = None

  def name(self):
    if not self.parent:
      return " -> " + self.link.name()
    else:
      return self.parent.getName(self.ind) + " -> " + self.link.name()

  def size(self):
    return 8

class FileSystem:
  def __init__(self):
    self.container = {}

  def create(self, file):
    ind = hash(file)
    self.container[ind] = file
    return ind

  def find(self, name):
    for value in self.container.itervalues():
      if value.name() == name:
        return value
    return None

  def printAll(self):
    for value in self.container.itervalues():
      if isinstance(value, File):
        print "file: %8d %s" % (value.size, value.name())
      elif isinstance(value, Dir):
        print " dir: %8d %s" % (value.size(), value.name())
      elif isinstance(value, Link):
        print "link: %8d %s" % (value.size(), value.name())

class FileFacade:
  def __init__(self):
    global fileSystem
    fileSystem = FileSystem()

  def createFile(self, size):
    file = File(size)
    fileSystem.create(file)
    return file

  def createDir(self):
    file = Dir()
    fileSystem.create(file)
    return file

  def createRoot(self):
    file = Root()
    fileSystem.create(file)
    return file

  def createLink(self, link):
    file = Link(link)
    fileSystem.create(file)
    return file

  def add(self, folder, name, file):
    folder.add(name, file)

  def find(self, name):
    return fileSystem.find(name)

  def printAll(self):
    fileSystem.printAll()

if __name__ == "__main__":
  fileFacade = FileFacade()
  root = fileFacade.createRoot()
  etc = fileFacade.createDir()
  fileFacade.add(root, "etc", etc)
  home = fileFacade.createDir()
  fileFacade.add(root, "home", home)
  user = fileFacade.createDir()
  fileFacade.add(home, "user", user)
  fileFacade.add(user, "readme.txt", fileFacade.createFile(1177))
  fileFacade.add(user, ".etc", fileFacade.createLink(etc))
  fileFacade.printAll()
  print "find('/home/user')=", fileFacade.find("/home/user")
  print "find('/home/user/')=", fileFacade.find("/home/user/")

В результате выполнения кода имеем повтор результата базового примера с точностью до перестановки строк вывода:

 dir:        2 /home/user/
file:     1177 /home/user/readme.txt
link:        8 /home/user/.etc -> /etc/
 dir:        2 /
 dir:        0 /etc/                                                                                
 dir:        1 /home/                                                                               
find('/home/user')= None                                                                            
find('/home/user/')= <__main__.Dir instance at 0x7f5c4efb8c20>                                      

PS. Если к этому моему начинанию будет проявлен интерес я готов продолжить работу в этом направлении и буду рад если кто-то меня поддержит или предложит альтернативу. У меня в данный момент готовы также примеры для

  • Behavioral: chainofresponsibility, observer;
  • Creational: builder, factorymethod, prototype;
  • Structural: adapter, bridge, composite, decorator, flyweight, proxy.

Автор: bya

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js