草庐IT

Python设计模式-结构型:适配器模式,装饰者模式,代理模式,组合模式,外观模式

OCEANEYES.GZY 2023-03-28 原文

Python设计模式-结构型:适配器模式,装饰者模式,代理模式,组合模式,外观模式

  • 适配器模式定义及简单实现案例
  • 装饰者模式定义及简单实现案例
  • 代理模式定义及简单实现案例
  • 组合模式定义及简单实现案例
  • 外观模式定义及简单实现案例

适配器模式 adapter

电子产品的电源插头插在转换插头上,然后转换插头插上电源,电子产品就能正常工作了。这就是适配器模式

# -*- coding: utf-8 -*-
class OldCourse(object):
    """
    老的课程类
    """

    def show(self):
        """
        显示关于本课程的所有信息
        """
        print("show description")
        print("show teacher of course")
        print("show labs")


class Page(object):
    """
    使用课程对象的客户端
    """

    def __init__(self, course):
        self.course = course

    def render(self):
        self.course.show()


class NewCourse(object):
    """
    新的课程类, 为了模块化显示课程信息,实现了新的课程类
    """
    def show_desc(self):
        """
        显示描述信息
        """
        print("show description")

    def show_teacher(self):
        """
        显示老师信息
        """
        print("show teacher of course")

    def show_labs(self):
        """
        显示实验
        """
        print("show labs")


class Adapter(object):
    """
    适配器, 尽管实现了新的课程类,但是在很多代码中还是需要使用 OldCourse.show() 方法
    """

    def __init__(self, course):
        self.course = course

    def show(self):
        """
        适配方法,调用真正的操作
        """
        self.course.show_desc()
        self.course.show_teacher()
        self.course.show_labs()


if __name__ == '__main__':
    old_course = OldCourse()
    page = Page(old_course)
    page.render()
    print("")
    new_course = NewCourse()
    # 新课程类没有 show 方法,我们需要使用适配器进行适配
    adapter = Adapter(new_course)
    page = Page(adapter)
    page.render()

适配器模式就是把一个类的接口变换成客户端所期待的另一种接口,使原本因接口不兼容而无法在一起工作的两个类能够在一起工作。

装饰者模式 Decorator

装饰者模式能动态的给对象添加行为。如果你对 Flask 比较熟悉的话,应该知道在使用 Flask-Login 的时候可以使用 login_required 装饰器包装一个需要用户登录访问的view

# -*- coding: utf-8 -*-
from functools import wraps

HOST_DOCKER = 0


def docker_host_required(f):
    """
    装饰器,必须要求 host 类型是 HOST_DOCKER
    """
    @wraps(f)
    def wrapper(*args, **kwargs):
        if args[0].type != HOST_DOCKER:
            raise Exception("Not docker host")
        else:
            return f(*args, **kwargs)
    return wrapper


class Host(object):
    """
    host 类
    """

    def __init__(self, type):
        self.type = type

    # 装饰这一方法
    @docker_host_required
    def create_container(self):
        print("create container")


if __name__ == '__main__':
    # 初始化 Host
    host = Host(HOST_DOCKER)
    host.create_container()
    print("")
    # 再次初始化 Host
    host = Host(1)
    host.create_container()

在上面的代码中,Host有一个方法Host.create_container,只有当Host实例的类型是DOCKER_HOST的时候才能执行该方法。为了加上这一行为,我们使用了装饰者模式。可以看出使用装饰者模式,我们可以动态改变类的行为,同时能提高代码复用性,因为任何类型为HOST_DOCKER的Host都可以使用该装饰器。另外要说明下:为了更好的实现装饰器,我们使用functools.wrap函数。

代理模式 proxy

所谓代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的访问。通过代理,我们可以对访问做一些控制。在开发网站的过程中,针对一些频繁访问的资源,我们会使用缓存。

# -*- coding: utf-8 -*-
from time import sleep


class Redis(object):
    """
    用于模拟 redis 服务
    """

    def __init__(self):
        """
        使用字典存储数据
        """
        self.cache = dict()

    def get(self, key):
        """
        获取数据
        """
        return self.cache.get(key)

    def set(self, key, value):
        """
        设置数据
        """
        self.cache[key] = value


class Image(object):
    """
    图片对象,图片存在七牛云存储中,我们只保存了一个地址
    """

    def __init__(self, name):
        self.name = name

    @property
    def url(self):
        sleep(2)
        return "https://dn-syl-static.qbox.me/img/logo-transparent.png"


class Page(object):
    """
    用于显示图片
    """

    def __init__(self, image):
        """
        需要图片进行初始化
        """
        self.image = image

    def render(self):
        """
        显示图片
        """
        print(self.image.url)


redis = Redis()


class ImageProxy(object):
    """
    图片代理,首次访问会从真正的图片对象中获取地址,以后都从 Redis 缓存中获取
    """

    def __init__(self, image):
        self.image = image

    @property
    def url(self):
        addr = redis.get(self.image.name)
        if not addr:
            addr = self.image.url
            print("Set url in redis cache!")
            redis.set(self.image.name, addr)
        else:
            print("Get url from redis cache!")
        return addr


if __name__ == '__main__':
    img = Image(name="logo")
    proxy = ImageProxy(img)
    page = Page(proxy)
    # 首次访问
    page.render()
    print("")
    # 第二次访问
    page.render()

代理对象和真实的对象之间都实现了共同的接口,这使我们可以在不改变原接口情况下,使用真实对象的地方都可以使用代理对象。其次,代理对象在客户端和真实对象之间直接起到了中介作用,同时通过代理对象,我们可以在将客户请求传递给真实对象之前做一些必要的预处理。

组合模式 composite

什么是组合模式?按照定义来说,组合模式是将对象组合成树形结构表示,使得客户端对单个对象和组合对象的使用具有一致性。组合模式的使用通常会生成一棵对象树,对象树中的叶子结点代表单个对象,其他节点代表组合对象。调用某一组合对象的方法,其实会迭代调用所有其叶子对象的方法。

使用组合模式的经典例子是 Linux 系统内的树形菜单和文件系统。在树形菜单中,每一项菜单可能是一个组合对象,其包含了菜单项和子菜单,这样就形成了一棵对象树。在文件系统中,叶子对象就是文件,而文件夹就是组合对象,文件夹可以包含文件夹和文件,同样又形成了一棵对象树。同样的例子还有员工和领导之间的关系

# -*- coding: utf-8 -*-
import abc


class Worker(object):
    """
    员工抽象类
    """
    __metaclass__ = abc.ABCMeta

    def __init__(self, name):
        self.name = name

    @abc.abstractmethod
    def work(self):
        pass


class Employe(Worker):
    """
    员工类
    """
    __metaclass__ = abc.ABCMeta

    def work(self):
        print("Employ: %s start to work " % self.name)


class Leader(Worker):
    """
    领导类
    """

    def __init__(self, name):
        self.members = []
        super(Leader, self).__init__(name)

    def add_member(self, employe):
        if employe not in self.members:
            self.members.append(employe)

    def remove_member(self, employe):
        if employe in self.members:
            self.members.remove(employe)

    def work(self):
        print("Leader: %s start to work" % self.name)
        for employe in self.members:
            employe.work()


if __name__ == '__main__':
    employe_1 = Employe("employe_1")
    employe_2 = Employe("employe_2")
    leader_1 = Leader("leader_1")
    leader_1.add_member(employe_1)
    leader_1.add_member(employe_2)

    employe_3 = Employe("employe_3")
    leader_2 = Leader("leader_2")
    leader_2.add_member(employe_3)
    leader_2.add_member(leader_1)

    leader_2.work()

外观模式 facade

所谓外观模式,就是将各种子系统的复杂操作通过外观模式简化,让客户端使用起来更方便简洁。

  • 比如你夏天晚上出门时,要关闭电灯,关闭电视机,关闭空调,如果有了一个总开关,通过它可以关闭电灯,电视机和空调,你出门的时候关闭总开关就行了。
    • 在这个例子中,你就是客户端,总开关就是外观模式的化身
# -*- coding: utf-8 -*-


class User(object):
    """
    用户类
    """
    def is_login(self):
        return True

    def has_privilege(self, privilege):
        return True


class Course(object):
    """
    课程类
    """
    def can_be_learned(self):
        return True


class Lab(object):
    """
    实验类
    """
    def can_be_started(self):
        return True


class Client(object):
    """
    客户类,用于开始一个实验
    """
    def __init__(self, user, course, lab):
        self.user = user
        self.course = course
        self.lab = lab

    def start_lab(self):
        """
        开始实验,需要一系列的判断:用户是否登录,课程是否可以学习,实验是否可以开始。判断非常繁琐!
        """
        if self.user.is_login() and self.course.can_be_learned() and self.lab.can_be_started():
            print("start lab")
        else:
            print("can not start lab")


class FacadeLab(object):
    """
    新的Lab类,应用了面向对象模式
    """

    def __init__(self, user, course, lab):
        self.user = user
        self.course = course
        self.lab = lab

    def can_be_started(self):
        if self.user.is_login() and self.course.can_be_learned() and self.lab.can_be_started():
            return True
        else:
            return False


class NewClient(object):
    """
    新的客户类,使用外观模式
    """
    def __init__(self, facade_lab):
        self.lab = facade_lab

    def start_lab(self):
        """
        开始实验,只需要判断 FacadeLab 是否可以开始
        """
        if self.lab.can_be_started:
            print("start lab")
        else:
            print("can not start lab")


if __name__ == '__main__':
    user = User()
    course = Course()
    lab = Lab()
    client = Client(user, course, lab)
    client.start_lab()

    print("Use Facade Pattern:")
    facade_lab = FacadeLab(user, course, lab)
    facade_client = NewClient(facade_lab)
    facade_client.start_lab()

外观模式的主要目的在于降低系统的复杂程度,在面向对象软件系统中,类与类之间的关系越多,不能表示系统设计得越好,反而表示系统中类之间的耦合度太大,这样的系统在维护和修改时都缺乏灵活性,因为一个类的改动会导致多个类发生变化,而外观模式的引入在很大程度上降低了类与类之间的耦合关系。引入外观模式之后,增加新的子系统或者移除子系统都非常方便,客户类无须进行修改(或者极少的修改),只需要在外观类中增加或移除对子系统的引用即可。

有关Python设计模式-结构型:适配器模式,装饰者模式,代理模式,组合模式,外观模式的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - capybara field.has_css?匹配器 - 2

    我在MiniTest::Spec和Capybara中使用以下规范:find_field('Email').must_have_css('[autofocus]')检查名为“电子邮件”的字段是否具有autofocus属性。doc说如下:has_css?(path,options={})ChecksifagivenCSSselectorisonthepageorcurrentnode.据我了解,字段“Email”是一个节点,因此调用must_have_css绝对有效!我做错了什么? 最佳答案 通过JonasNicklas得到了答案:No

  5. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  6. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  7. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  8. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

随机推荐