设计模式
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。
- 对接口编程而不是对实现编程。
- 优先使用对象组合而不是继承。
序号 | 模式 & 描述 | 包括 |
---|---|---|
1 | 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 | 工厂模式(Factory Pattern)抽象工厂模式(Abstract Factory Pattern)单例模式(Singleton Pattern)建造者模式(Builder Pattern)原型模式(Prototype Pattern) |
2 | 结构型模式 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 | 适配器模式(Adapter Pattern)桥接模式(Bridge Pattern)过滤器模式(Filter、Criteria Pattern)组合模式(Composite Pattern)装饰器模式(Decorator Pattern)外观模式(Facade Pattern)享元模式(Flyweight Pattern)代理模式(Proxy Pattern) |
3 | 行为型模式 这些设计模式特别关注对象之间的通信。 | 责任链模式(Chain of Responsibility Pattern)命令模式(Command Pattern)解释器模式(Interpreter Pattern)迭代器模式(Iterator Pattern)中介者模式(Mediator Pattern)备忘录模式(Memento Pattern)观察者模式(Observer Pattern)状态模式(State Pattern)空对象模式(Null Object Pattern)策略模式(Strategy Pattern)模板模式(Template Pattern)访问者模式(Visitor Pattern) |
4 | J2EE 模式 这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。 | MVC 模式(MVC Pattern)业务代表模式(Business Delegate Pattern)组合实体模式(Composite Entity Pattern)数据访问对象模式(Data Access Object Pattern)前端控制器模式(Front Controller Pattern)拦截过滤器模式(Intercepting Filter Pattern)服务定位器模式(Service Locator Pattern)传输对象模式(Transfer Object Pattern) |
六大原则
单一职责原则(Single Responsibility Principle)
单一职责原则的定义是:应该有且仅有一个原因引起类的变更。
单一职责原则不仅适用于接口和类,也适用于方法。一个方法尽可能只做一件事。
开闭原则(Open Closed Principle)
https://blog.csdn.net/lovelion/article/details/7537584
开闭原则是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。软件实体应尽量在不修改原有代码的情况下进行扩展。
软件实体可以指一个软件模块,一个由多个类组成的局部结构或一个独立的类。
抽象化是开闭原则的关键——Q:什么是抽象呢?
例:
新加一个图表就要新加if条件判断逻辑。
重构:
里氏替换原则(Liskov Substitution Principle)
单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
优点:
- 内存里只有一个实例,减少了内存开销。(频繁的创建和销毁实例)
- 避免对资源的多重占用。(比如写文件操作)
缺点:
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
C++实现
懒汉式(lazy-Initialzation)
直到使用时才实例化对象,即直到调用GetInstance时才new一个对象。
1 | class Singleton |
构造函数是私有的,导致在外部任何尝试创建实例的尝试都会失败。
禁止拷贝和赋值。
唯一方法是通过调用公有静态函数GetInstance,返回的实例是该函数首次被访问时创建的。
系统会在程序结束后释放所有全局变量并析构所有类的静态对象。
饿汉式
1 | //Singleton.h |
与懒汉式区别在于,在全局作用域进行单例类的实例化,并且此实例初始化单例类的静态成员指针。
Meyers’s Singleton
1 | class Singleton |
1 | static Singleton* GetInstance() |
线程安全的单例模式
1 | Singleton* Singleton::GetInstance() |
每次获取实例的时候都要先上锁,之后在解锁,如果有很多线程的话,可能会造成大量线程阻塞。
1 | Singleton* Singleton::GetInstance() |
改进后,绝大多数情况下都是直接返回实例,只有在没有实例的时候,才会上锁、解锁。
How should it be used
You need to have one and only one object of a type in system
你需要系统中只有唯一一个实例存在的类的全局变量的时候才使用单例。
How to create the best singleton:
- The smaller, the better. I am a minimalist
- Make sure it is thread safe
- Make sure it is never null
- Make sure it is created only once
- Lazy or system initialization? Up to your requirements
- Sometimes the OS or the JVM creates singletons for you (e.g. in Java every class definition is a singleton)
- Provide a destructor or somehow figure out how to dispose resources
- Use little memory
越小越好,越简单越好,线程安全,内存不泄露
“Singleton仅仅是一个包装好的全局变量,那你能说说它和全局变量的相同与不同么?”
单例可以说是全局变量的替代品。其拥有全局变量的众多特点:
全局可见且贯穿应用程序的整个生命周期。除此之外,单例模式还拥有一些全局变量所不具有的性质:同一类型的对象实例只能有一个,同时适当的实现还拥有延迟初始化(Lazy)的功能,可以避免耗时的全局变量初始化所导致的启动速度不佳等问题。要说明的是,Singleton的最主要目的并不是作为一个全局变量使用,而是保证类型实例有且仅有一个。它所具有的全局访问特性仅仅是它的一个副作用。但正是这个副作用使它更类似于包装好的全局变量,从而允许各部分代码对其直接进行操作。软件开发人员需要通过仔细地阅读各部分对其进行操作的代码才能了解其真正的使用方式,而不能通过接口得到组件依赖性等信息。如果Singleton记录了程序的运行状态,那么该状态将是一个全局状态。各个组件对其进行操作的调用时序将变得十分重要,从而使各个组件之间存在着一种隐式的依赖。
对象释放问题
程序中只有new没有delete
A:一般情况下单例模式的实例是常驻内存的,因此不需要手动释放,如果的确需要释放实例占用的内存,一定不能在单例内的析构函数中进行delete操作,这样会造成无限循环。
如果在类的析构函数中有必要的操作,例如关闭文件,释放外部资源,需要正常删除实例。
bad idea:可以在程序结束时调用GetInstance,然后delete,但是delete之后无法保证后面还有没有人调用GetInstance。
一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。
程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的CGarbo类(Garbo意为垃圾工人):
1 | class CSingleton |
Reference:
https://www.cnblogs.com/leaves1024/p/10985599.html
https://www.cnblogs.com/faith0217/articles/4083476.html
https://zhuanlan.zhihu.com/p/37469260
工厂模式
简单工厂模式
工厂类:创建指定具体实例对象的接口
抽象产品类:具体产品类的父类或接口
具体产品类:工厂类创建的对象是具体产品类的实例
特点:工厂类封装了创建产品对象的函数
缺点:扩展差,新增产品的时候需要修改工厂类
例:
1 | // 鞋子抽象类 |
1 | int main() |
工厂模式
情景:为了生产某种鞋子,需要为不同牌子的鞋开设独立的生产线。
抽象工厂类:提供创建具体产品类的接口
具体工厂类:继承抽象工厂,实现创建具体产品对象
抽象产品类:具体产品类的接口
具体产品类:具体工厂要创建的对象
特点:工厂部分抽象出了接口,实现交给子类
缺点:新增产品时,要增加对应产品的具体工厂类。
1 | // 总鞋厂 |
1 | int main() |
抽象工厂
情景:现在工厂不止产鞋子了,衣服裤子一起产了。
结构与工厂模式一样。
1 | // 基类 衣服 |
1 | int main() |
小结:
简单工厂模式,违背了开闭原则。
工厂和抽象工厂,增加产品类时需要增加对应的具体工厂类。
Reference:
https://zhuanlan.zhihu.com/p/83535678
https://zhuanlan.zhihu.com/p/83537599
MVC模式
Model-View-Controller(模型-视图-控制器模式)
- Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。
- View(视图) - 视图代表模型包含的数据的可视化。
- Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。
- Post link: https://github.com/TheBge/TheBge.github.io/2021/04/08/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub IssuesGitHub Discussions