设计模式
来源:互联网 发布:qq空间营销软件 编辑:程序博客网 时间:2024/06/05 18:18
饿汉法(饥渴式)
顾名思义,饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否
需要创建。代码如下:
public class Singleton {
private static Singleton = new Singleton();
private Singleton() {}
public static getSignleton(){
return singleton;
}
}
这样做的好处是编写简单,但是无法做到延迟创建对象。但是我们很多时候都希望
对象可以尽可能地延迟加载,从而减小负载,所以就需要下面的懒汉法:
懒汉式:
单线程写法
这种写法是最简单的,由私有构造器和一个公有静态工厂方法构成,在工厂方法中
对singleton进行null判断,如果是null就new一个出来,最后返回singleton对象
。这种方法可以实现延时加载,但是有一个致命弱点:线程不安全。如果有两条线
程同时调用getSingleton()方法,就有很大可能导致重复创建对象。
public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton() {
if(singleton == null) singleton = new Singleton();
return singleton;
}
}
考虑线程安全的写法
这种写法考虑了线程安全,将对singleton的null判断以及new的部分使用
synchronized进行加锁。同时,对singleton对象使用volatile关键字进行限制,
保证其对所有线程的可见性,并且禁止对其进行指令重排序优化。如此即可从语义
上保证这种单例模式写法是线程安全的。注意,这里说的是语义上,实际使用中还
是存在小坑的,会在后文写到。
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
return singleton;
}
}
兼顾线程安全和效率的写法
虽然上面这种写法是可以正确运行的,但是其效率低下,还是无法实际应用。因为
每次调用getSingleton()方法,都必须在synchronized这里进行排队,而真正遇
到需要new的情况是非常少的。所以,就诞生了第三种写法:
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
这种写法被称为“双重检查锁”,顾名思义,就是在getSingleton()方法中,进
行两次null检查。看似多此一举,但实际上却极大提升了并发度,进而提升了性能
。为什么可以提高并发度呢?就像上文说的,在单例中new的情况非常少,绝大多
数都是可以并行的读操作。因此在加锁前多进行一次null检查就可以减少绝大多数
的加锁操作,执行效率提高的目的也就达到了。
静态内部类法
那么,有没有一种延时加载,并且能保证线程安全的简单写法呢?我们可以把
Singleton实例放到一个静态内部类中,这样就避免了静态实例在Singleton类加
载的时候就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线
程安全的:
public class Singleton {
private static class Holder {
private static Singleton singleton = new Singleton();
}
private Singleton(){}
public static Singleton getSingleton(){
return Holder.singleton;
}
}
但是,上面提到的所有实现方式都有两个共同的缺点:
都需要额外的工作(Serializable、transient、readResolve())来实现序列化,
否则每次反序列化一个序列化的对象实例时都会创建一个新的实例。
可能会有人使用反射强行调用我们的私有构造器(如果要避免这种情况,可以修改
构造器,让它在创建第二个实例的时候抛异常)。
枚举写法
当然,还有一种更加优雅的方法来实现单例模式,那就是枚举写法:
public enum Singleton {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制
,防止反序列化的时候创建新的对象。因此,Effective Java推荐尽可能地使用
枚举来实现单例。
抽象工厂模式代码
产品类:
创建工厂类:
客户:
关于抽象工厂模式与工厂方法模式的区别,这里就不说了,感觉多看几遍例子就能理解,还有很多提到的产品族、等级结构等概念,说了反而更难理解。
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原
型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式
、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责
任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器
模式。
其实还有两类:并发型模式和线程池模式。
设计模式的六大原则:
总原则-开闭原则
对扩展开放,对修改封闭。在程序需要进行拓展的时候,不能去修改原有的代码,
而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序
的扩展性好,易于维护和升级。
想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提
到这点。
1、单一职责原则
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则
就应该把类拆分。
2、里氏替换原则(Liskov Substitution Principle)
任何基类可以出现的地方,子类一定可以出现。里氏替换原则是继承复用的基石,
只有当衍生类可以替换基类,软件单位的功能不受到影响时,基类才能真正被复用
,而衍生类也能够在基类的基础上增加新的行为。
里氏代换原则是对“开-闭”原则的补充。实现“开闭”原则的关键步骤就是抽象
化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现
抽象化的具体步骤的规范。里氏替换原则中,子类对父类的方法尽量不要重写和重
载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该
随便破坏它。
3、依赖倒转原则(Dependence Inversion Principle)
面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类
交互,而与具体类的上层接口交互。
4、接口隔离原则(Interface Segregation Principle)
每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使
用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
5、迪米特法则(最少知道原则)(Demeter Principle)
一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑
封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能
最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系
,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、
方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友
。我们要求陌生的类不要作为局部变量出现在类中。
6、合成复用原则(Composite Reuse Principle)
尽量首先使用合成/聚合的方式,而不是使用继承。
之前已经陆续整理了9种设计模式,链接如下,接下来一段时间陆续把剩余的过一
遍,整理出来,理解设计模式还是很重要的。
创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构型模式:适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模
式、享元模式
行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、
命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
还有两类:并发型模式和线程池模式。
观察者模式:
改变时主动发出通知。这通常通过呼叫各观察者所提供的方法来实现。此种模式通
常被用来实现事件处理系统。
角色
抽象被观察者角色:把所有对观察者对象的引用保存在一个集合中,每个被观察者
角色都可以有任意数量的观察者。被观察者提供一个接口,可以增加和删除观察者
角色。一般用一个抽象类和接口来实现。
抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自
己。
具体被观察者角色:在被观察者内部状态改变时,给所有登记过的观察者发出通知
。具体被观察者角色通常用一个子类实现。
具体观察者角色:该角色实现抽象观察者角色所要求的更新接口,以便使本身的状
态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保
存一个指向具体主题角色的引用。
适用场景
1) 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在
独立的对象中以使它们可以各自独立地改变和复用。
2) 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改
变。
3) 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不
希望这些对象是紧密耦合的。
应用
珠宝商运送一批钻石,有黄金强盗准备抢劫,珠宝商雇佣了私人保镖,警察局也派
人护送,于是当运输车上路的时候,强盗保镖警察都要观察运输车一举一动,
抽象的观察者
[java] view plain copy print?
public interface Watcher
{
public void update();
}
抽象的被观察者,在其中声明方法(添加、移除观察者,通知观察者):
[java] view plain copy print?
public interface Watched
{
public void addWatcher(Watcher watcher);
public void removeWatcher(Watcher watcher);
public void notifyWatchers();
}
具体的观察者
保镖
[java] view plain copy print?
public class Security implements Watcher
{
@Override
public void update()
{
System.out.println(“运输车有行动,保安贴身保护");
}
}
强盗
[java] view plain copy print?
public class Thief implements Watcher
{
@Override
public void update()
{
System.out.println(“运输车有行动,强盗准备动手");
}
}
警察
[java] view plain copy print?
public class Police implements Watcher
{
@Override
public void update()
{
System.out.println(“运输车有行动,警察护航");
}
}
具体的被观察者
[java] view plain copy print?
public class Transporter implements Watched
{
private List<Watcher> list = new ArrayList<Watcher>();
@Override
public void addWatcher(Watcher watcher)
{
list.add(watcher);
}
@Override
public void removeWatcher(Watcher watcher)
{
list.remove(watcher);
}
@Override
public void notifyWatchers(String str)
{
for (Watcher watcher : list)
{
watcher.update();
}
}
}
测试类
[java] view plain copy print?
public class Test
{
public static void main(String[] args)
{
Transporter transporter = new Transporter();
Police police = new Police();
Security security = new Security();
Thief thief = new Thief();
transporter.addWatcher(police);
transporter.addWatcher(security);
transporter.addWatcher(security);
transporter.notifyWatchers();
}
}
我推你拉
例子中没有关于数据和状态的变化通知,只是简单通知到各个观察者,告诉他们被
观察者有行动。
观察者模式在关于目标角色、观察者角色通信的具体实现中,有两个版本。
一种情况便是目标角色在发生变化后,仅仅告诉观察者角色“我变化了”,观察者
角色如果想要知道具体的变化细节,则就要自己从目标角色的接口中得到。这种模
式被很形象的称为:拉模式——就是说变化的信息是观察者角色主动从目标角色中
“拉”出来的。
还有一种方法,那就是我目标角色“服务一条龙”,通知你发生变化的同时,通过
一个参数将变化的细节传递到观察者角色中去。这就是“推模式”——管你要不要
,先给你啦。
这两种模式的使用,取决于系统设计时的需要。如果目标角色比较复杂,并且观察
者角色进行更新时必须得到一些具体变化的信息,则“推模式”比较合适。如果目
标角色比较简单,则“拉模式”就很合适啦。
工厂模式:
在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的。但是在一些情况下, new操作符直接生成对象会带来一些问题。举例来说, 许多类型对象的创造需要一系列的步骤: 你可能需要计算或取得对象的初始设置; 选择生成哪个子对象实例; 或在生成你需要的对象之前必须先生成一些辅助功能的对象。 在这些情况,新对象的建立就是一个 “过程”,不仅是一个操作,像一部大机器中的一个齿轮传动。
模式的问题:你如何能轻松方便地构造对象实例,而不必关心构造对象实例的细节和复杂过程呢?
解决方案:建立一个工厂来创建对象
实现:
一、引言
1)还没有工厂时代:假如还没有工业革命,如果一个客户要一款宝马车,一般的做法是客户去创建一款宝马车,然后拿来用。
2)简单工厂模式:后来出现工业革命。用户不用去创建宝马车。因为客户有一个工厂来帮他创建宝马.想要什么车,这个工厂就可以建。比如想要320i系列车。工厂就创建这个系列的车。即工厂可以创建产品。
3)工厂方法模式时代:为了满足客户,宝马车系列越来越多,如320i,523i,30li等系列一个工厂无法创建所有的宝马系列。于是由单独分出来多个具体的工厂。每个具体工厂创建一种系列。即具体工厂类只能创建一个具体产品。但是宝马工厂还是个抽象。你需要指定某个具体的工厂才能生产车出来。
4)抽象工厂模式时代:随着客户的要求越来越高,宝马车必须配置空调。于是这个工厂开始生产宝马车和需要的空调。
最终是客户只要对宝马的销售员说:我要523i空调车,销售员就直接给他523i空调车了。而不用自己去创建523i空调车宝马车.
这就是工厂模式。
二、分类
工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
工厂模式可以分为三类:
1)简单工厂模式(Simple Factory)
2)工厂方法模式(Factory Method)
3)抽象工厂模式(Abstract Factory)
这三种模式从上到下逐步抽象,并且更具一般性。
GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。
将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。
三、区别
工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以创建多个具体产品类的实例。
区别:
工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
两者皆可。
四、简单工厂模式
建立一个工厂(一个函数或一个类方法)来制造新的对象。
分布说明引子:从无到有。客户自己创建宝马车,然后拿来用。
客户需要知道怎么去创建一款车,客户和车就紧密耦合在一起了.为了降低耦合,就出现了工厂类,把创建宝马的操作细节都放到了工厂里面去,客户直接使用工厂的创建工厂方法,传入想要的宝马车型号就行了,而不必去知道创建的细节.这就是工业革命了:简单工厂模式
即我们建立一个工厂类方法来制造新的对象。如图:
产品类:
工厂类:
客户类:
简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
先来看看它的组成:
1) 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
2) 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
3) 具体产品角色:工厂类所创建的对象就是此角色的实例。在Java中由一个具体类实现。
下面我们从开闭原则(对扩展开放;对修改封闭)上来分析下简单工厂模式。当客户不再满足现有的车型号的时候,想要一种速度快的新型车,只要这种车符合抽象产品制定的合同,那么只要通知工厂类知道就可以被客户使用了。所以对产品部分来说,它是符合开闭原则的;但是工厂部分好像不太理想,因为每增加一种新型车,都要在工厂类中增加相应的创建业务逻辑(createBMW(int type)方法需要新增case),这显然是违背开闭原则的。可想而知对于新产品的加入,工厂类是很被动的。对于这样的工厂类,我们称它为全能类或者上帝类。
我们举的例子是最简单的情况,而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以这可能会把我们的上帝累坏了,也累坏了我们这些程序员。
于是工厂方法模式作为救世主出现了。 工厂类定义成了接口,而每新增的车种类型,就增加该车种类型对应工厂类的实现,这样工厂的设计就可以扩展了,而不必去修改原来的代码。
五、工厂方法模式
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
工厂方法模式组成:
1)抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
2)具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的“上帝类”。正如上面所说,这样便分担了对象承受的压力;而且这样使得结构变得灵活 起来——当有新的产品产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有 的代码。可以看出工厂角色的结构也是符合开闭原则的!
代码如下:
产品类:
创建工厂类:
客户类:
工厂方法模式仿佛已经很完美的对对象的创建进行了包装,使得客户程序中仅仅处理抽象产品角色提供的接口,但使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 设计模式~~~~~~~~~~
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 设计模式
- 求协方差的公式是什么?
- 【XSY2484】mex 离散化 线段树
- 结构体
- set
- 顺序表
- 设计模式
- 解决使用offsetWidth让DIV自动变宽时出现的小BUG。
- backdoorppt:一个可将载荷伪装成PPT的工具
- mnist svm
- [IOS笔记]懒加载—购物车加载数组和字典
- html超链接
- beyond compare 找不到靠谱“绿色版”怎么办
- Redis内存使用优化与存储
- python argparse 用法总结