设计模式——装饰者模式 Java源代码
最后更新于:2022-04-01 20:02:06
装饰者模式,可以动态地把职责附加到已有的对象上面去。又称 Wrapper Pattern,在代码实现的时候,确实有“包装”的意思。
# 类图
![装饰者](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56cab10d6335a.jpg "")
图:来自《Head First Design Patterns》
可以看出,装饰者模式里面,有4个角色:Component抽象类,ConcreteComponent具体类,Decorator抽象类,ConcreteDecorator具体类
![装饰者模式](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56cab10d8b732.jpg "")
图:我的Java源代码的类图
总共10个类
一个Component抽象类
两个ConcreteComponent具体类
一个Decorator抽象类
五个ConcreteDecorator具体类
一个Main测试类
ConcreteDecorator **继承**了Decorator抽象类, **组合**了Component抽象类。Decorator抽象类继承了Component抽象类,这个继承的目的是:it’s vital that the decorators have the same type as the object they are going to decorate。简而言之,就是为了获得相同的类型,并不是为了获得行为。组合是为了让所有的concreteDecorator可以“互相包装、装饰”
# talk is cheap, show me the code
去食堂打菜,经济套餐,假设总共只有5种食物可供选择分别是:手撕包菜,茄子豆角,麻婆豆腐,青椒肉丝,红烧里脊。
### 一个Component抽象类
~~~
package decorator;
public abstract class Meal
{
String description = "Unknown Meal";
public String getDescription()
{
return description;
}
public abstract double cost();
}
~~~
### 两个ConcreteComponent具体类
~~~
package decorator;
public class ChineseMeal extends Meal
{
public ChineseMeal()
{
description = "Chinese Meal";
}
@Override
public double cost()
{
return 1.0; // 一次性餐具费用
}
}
~~~
~~~
package decorator;
public class WesternMeal extends Meal
{
public WesternMeal()
{
description = "Western meal";
}
@Override
public double cost()
{
return 5.0; // 不锈钢刀、叉费用
}
}
~~~
### 一个Decorator抽象类
~~~
package decorator;
public abstract class Decorator extends Meal
{
public abstract String getDescription();
}
~~~
### 五个ConcreteDecorator具体类
~~~
package decorator;
// 茄子豆角
public class EggplantBean extends Decorator
{
Meal meal;
public EggplantBean(Meal meal)
{
this.meal = meal;
}
@Override
public String getDescription()
{
return meal.getDescription() + ", 茄子豆角";
}
@Override
public double cost()
{
return 2 + meal.cost();
}
}
~~~
~~~
package decorator;
// 青椒炒肉
public class GreenPepperPork extends Decorator
{
Meal meal;
public GreenPepperPork(Meal meal)
{
this.meal = meal;
}
@Override
public String getDescription()
{
return meal.getDescription() + ", 青椒炒肉";
}
@Override
public double cost()
{
return 3 + meal.cost();
}
}
~~~
~~~
package decorator;
// 手撕包菜
public class HandCabbage extends Decorator
{
Meal meal;
public HandCabbage(Meal meal)
{
this.meal = meal;
}
@Override
public String getDescription()
{
return meal.getDescription() + ", 手撕包菜";
}
@Override
public double cost()
{
return 2 + meal.cost();
}
}
~~~
~~~
package decorator;
// 麻婆豆腐
public class MapoTofu extends Decorator
{
Meal meal;
public MapoTofu(Meal meal)
{
this.meal = meal;
}
@Override
public String getDescription()
{
return meal.getDescription() + ", 麻婆豆腐";
}
@Override
public double cost()
{
return 4 + meal.cost();
}
}
~~~
~~~
package decorator;
// 红烧里脊
public class BraiseTenderloin extends Decorator
{
Meal meal;
public BraiseTenderloin(Meal meal)
{
this.meal = meal;
}
@Override
public String getDescription()
{
return meal.getDescription() + ", 红烧里脊";
}
@Override
public double cost()
{
return 5 + meal.cost();
}
}
~~~
### 一个Main测试类
~~~
package decorator;
public class Main
{
public static void main(String[] args)
{
Meal meal = new ChineseMeal();
meal = new GreenPepperPork(meal);
meal = new HandCabbage(meal);
meal = new BraiseTenderloin(meal);
System.out.println(meal.getDescription() + " ¥" + meal.cost());
Meal meal2 = new ChineseMeal();
meal2 = new EggplantBean(new MapoTofu(new BraiseTenderloin(meal2)));
System.out.println(meal2.getDescription() + " ¥" + meal2.cost());
}
}
~~~
# 运行结果
直接从eclipse复制过来
~~~
Chinese Meal, 青椒炒肉, 手撕包菜, 红烧里脊 ¥11.0
Chinese Meal, 红烧里脊, 麻婆豆腐, 茄子豆角 ¥12.0
~~~
# 分析讨论
去食堂打菜,经济套餐,假设总共只有5种食物可供选择分别是:手撕包菜,茄子豆角,麻婆豆腐,青椒肉丝,红烧里脊。如果可以随意打菜,就可以有C15+C25+C35+C45+C55=25−1=31 种选择。如果写代码把这120种选择都分别弄成一个类,就有31个类。这样不仅代码重复率太高,而且如果食堂**更改**了上述五样菜中一样菜的**价格**,那么**31个类的代码需要全部修改**。这不符合开闭原则,不利于代码的维护和扩展。
装饰者模式就可以解决上述这种“类的爆炸式增长”问题,各种各样的排列组合太多,不能在代码中一个类一个。
装饰者模式的特点是“继承(inheritance) + 组合(Composition)”:继承是为了让component和decorator拥有相同的类型,组合是为了让所有的concreteDecorator可以“互相包装、装饰”。装饰者又名Wrapper大概就是这样来的。
# Java. IO
Java.IO 中应用了装饰者模式
~~~
FileInputStream fiStream = null;
InputStreamReader iStreamReader = null;
BufferedReader bReader = null;
fiStream = new FileInputStream("C:\\xxxx");
// InputStreamReader 是字节流通向字符流的桥梁、
iStreamReader = new InputStreamReader(fiStream);
// 从字符输入流中读取文件中的内容,装饰了一个InputStreamReader的对象
bReader = new BufferedReader(iStreamReader("C:\\xxxx"));
~~~
~~~
一句代码版本,把3个new放到一起
BufferedReader bReader = new BufferedReader(new InputStreamReader(new FileInputStream("C:\\xxxx"));
~~~
';
设计模式——适配器模式 Java源代码
最后更新于:2022-04-01 20:02:03
# 前言
《Head First Design Patterns》给的代码的例子是关于鸭子和火鸡,然而鸭子和火鸡离日常生活比较远。这次,我改编了实验楼网站上面的例子,关于插座和充电器。
![充电插头](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56cab10d11f19.jpg "")
图:不同国家的插座,插头不一样,呵呵哒
![适配器](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56cab10d2ba95.jpg "")
图:所以需要写一个适配器模式
![适配器类图](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56cab10d4616c.jpg "")
图:我绘制的适配器类图
情景:美国的插座,提供110伏电压;中国的插座,提供220伏电压。
1. 在中国,用两孔插座充电
1. 然后坐飞机去美国旅游,假设美国某旅馆的墙上有只有一个三孔插座
1. 幸好我有美国适配器,一头插到三孔插座,另一头转换成二孔插座,就可以给我的荣耀手机充电
1. 在美国,通过美国适配器,用三空插座充电
总共7个类
一个三孔插座接口(Adaptee, 被适配者)
一个三孔插座类
一个两孔插座接口(Target, 适配目标)
一个两孔插座类
一个适配器(Adapter:**实现Target, 组合Adaptee**)
一个手机类(Client)
一个Main类,用于测试
# talk is cheap, show me the code
### 三孔插座接口(Adaptee)
~~~
package adapter;
// adaptee(被适配者) ———— 假设在美国某旅馆的墙上,只有一个三孔插座
public interface ThreePinSoket
{
public void chargeWithThreePin();
public int voltage();
}
~~~
### 三孔插座类
~~~
package adapter;
// 实现一个具体的 adaptee
public class ThreePinSoketAmerica implements ThreePinSoket
{
@Override
public void chargeWithThreePin()
{
System.out.println("美国标准的三孔的插座");
}
@Override
public int voltage()
{
return 110; // 美国电压是110伏
}
}
~~~
### 两孔插座接口(Target)
~~~
package adapter;
// target(适配目标) ———— 我的荣耀手机充电器是两个插头,所以需要两个插孔的插座
public interface TwoPinSoket
{
public void chargeWithTwoPin();
public int voltage();
}
~~~
### 两孔插座类
~~~
package adapter;
// client(具体的adaptee) ———— 这个就是我在中国的墙上的两个插孔的插座,我充电只能用这个
public class TwoPinSoketChina implements TwoPinSoket
{
@Override
public void chargeWithTwoPin()
{
System.out.println("中国标准的两孔的插座");
}
@Override
public int voltage()
{
return 220; // 中国电压是220伏
}
}
~~~
### 适配器(Adapter)
**实现Target, 组合Adaptee**
~~~
package adapter;
// 去美国旅游,必须带上一个“美国适配器”:实现两孔插座,组合三孔插座。用来给我的荣耀手机充电
public class AmericaAdapter implements TwoPinSoket // 实现两孔插座(target)
{
ThreePinSoket threePinSoket; // 组合三孔插座(adaptee)
public AmericaAdapter(ThreePinSoket threePinSoket)
{
this.threePinSoket = threePinSoket;
}
@Override
public void chargeWithTwoPin()
{
threePinSoket.chargeWithThreePin();
}
@Override
public int voltage()
{
return threePinSoket.voltage() * 2; // 适配器把电压从 110V 升到 220V
}
}
~~~
### 手机类(Client)
~~~
package adapter;
public class RongYao
{
TwoPinSoket twoPinSoket;
public RongYao() {}
public void setTwoPinSoket(TwoPinSoket twoPinSoket)
{
this.twoPinSoket = twoPinSoket;
}
public void chargeRequest()
{
System.out.println("华为荣耀手机, " + twoPinSoket.voltage() + " 伏特充电中\n");
}
}
~~~
### Main类,用于测试
~~~
package adapter;
public class Main
{
public static void main(String[] args)
{
// 在中国,用两孔插座充电
TwoPinSoketChina twoPinSoketChina = new TwoPinSoketChina();
RongYao myRongYao = new RongYao();
myRongYao.setTwoPinSoket(twoPinSoketChina);
myRongYao.chargeRequest();
// 然后坐飞机去美国旅游,美国某旅馆的墙上有只有一个三孔插座
ThreePinSoketAmerica threePinSoketAmerica = new ThreePinSoketAmerica();
testThreePin(threePinSoketAmerica);
// 幸好我有美国适配器,一头插到三孔插座,另一头转换成二孔插座,就可以给我的荣耀手机充电
AmericaAdapter americaAdapter = new AmericaAdapter(threePinSoketAmerica);
testTwoPin(americaAdapter);
// 在美国,通过美国适配器,用三空插座充电
myRongYao.setTwoPinSoket(americaAdapter);
myRongYao.chargeRequest();
}
static void testTwoPin(TwoPinSoket twoPinSoket)
{
twoPinSoket.chargeWithTwoPin();
System.out.println("电压是" + twoPinSoket.voltage() + "伏特\n");
}
static void testThreePin(ThreePinSoket threePinSoket)
{
threePinSoket.chargeWithThreePin();
System.out.println("电压是" + threePinSoket.voltage() + "伏特\n");
}
}
~~~
# 运行结果
直接从eclipse复制过来
~~~
华为荣耀手机, 220 伏特充电中
美国标准的三孔的插座
电压是110伏特
美国标准的三孔的插座
电压是220伏特
华为荣耀手机, 220 伏特充电中
~~~
# 分析
适配器模式有三个重要角色:
- 目标角色(Target),要**转换成**的目标接口。在我的代码例子中,是中国的两孔接口
- 源角色(Adaptee),需要**被转换**的源接口。在我的代码例子中,是美国的三孔接口
- 适配器角色(Adapter),核心是**实现Target接口, 组合Adaptee接口**
这样,Adaptee和Target两个原本不兼容的接口,就可以在一起工作了(我的荣耀手机就可以在美国充电了)。这里的面向接口编程,得到了松耦合的效果。
美国的三孔插座可以实现Adaptee接口,那么英国、法国的三孔插座也可以去实现Adaptee接口,它们都成为了Adaptee接口的子类。在Adapter类中,由于组合了一个Adaptee的引用,根据Java的多态性,我就可以拿着相同的Adapter类去英国,法国充电了。
另一方面,Client类组合一个Target接口的引用。我们就可制造多个Adapter类,实现同一个Target接口。假设索尼手机的需要日本标准的两孔插座,那么写一个日本两孔插座类实现Target接口,我就可以拿着相同的Adapter类,在美国给日本的索尼手机充电了。
最后补充一点:《Head First Design Patterns》说到,适配器模式其实分为两种。一种是Object Adapter,另外一种是Class Adapter。本篇博客就是一个Object Adapter的例子。那么Class Adapter是长什么样子的呢?它**继承Adaptee类,实现Target接口** 。《设计模式 (Java版)》中的例子就是这样的。
[更多设计模式,在新标签页中打开这里~](http://blog.csdn.net/u013390476/article/details/50333763)
';
设计模式——观察者模式 Java源代码
最后更新于:2022-04-01 20:02:01
本程序改编自《Head First Design Patterns》上关于气象站的例子,我将例子进行了化简。
总共7个java源文件
一个Subject接口
一个Observer接口
一个DisplayElement接口
一个Subject具体类,实现Subject接口
两个Observer具体类,实现Observer和DisplayElement接口
一个Main类,用于测试
观察者模式类图
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56cab10ce9590.jpg "")
图:我用插件画的类图
The Observation Pattern defines a one-to-many dependency。这里的one就是Subject, many就是Observers;one就是发布者,many就是订阅者;one就是事件源,many就是监听者。
# Talk is cheap, show me the code
### 一个Subject接口
~~~
package observer;
public interface Subject
{
public void registerObserver(Observer o);
public void removeOvserver(Observer o);
public void notifyObservers();
}
~~~
### 一个Observer接口
~~~
package observer;
public interface Observer
{
public void update(double temperature, double humidity, String condition);
}
~~~
### 一个DisplayElement接口
这个接口只有display( )一个抽象方法,其实如果放在Observer里面的话,仅从代码层面考虑的话,也可以。但是如果从逻辑层面考虑的话,最好还是分开。毕竟这个display和观察者是”两码事“,不要耦合在一起。《Head First Design Pattern》上面的例子代码这样写,是为了描绘出一个生动形象的”气象站“的例子,来帮助大家理解观察者模式。
~~~
package observer;
public interface DisplayElement
{
public void display();
}
~~~
### 一个Subject具体类
~~~
package observer;
import java.util.ArrayList;
// Subject收集天气信息,然后通知Observers
public class WeatherData implements Subject
{
private ArrayList observers;
private double temperature;
private double humidity;
private String condition;
public WeatherData()
{
observers = new ArrayList();
}
@Override
public void registerObserver(Observer o)
{
observers.add(o);
}
@Override
public void removeOvserver(Observer o)
{
int i = observers.indexOf(o);
if(i>=0)
{
observers.remove(i);
}
}
@Override
public void notifyObservers()
{
for(Observer observer : observers)
{
observer.update(temperature, humidity, condition);
}
}
public void measurementsChanged()
{
notifyObservers();
}
public void setMeasurements(double temperature, double humidity, String condition)
{
this.temperature = temperature;
this.humidity = humidity;
this.condition = condition;
measurementsChanged();
}
public double getTemperature()
{
return temperature;
}
public double getHumidity()
{
return humidity;
}
public String getPressure()
{
return condition;
}
}
~~~
### 两个Observer具体类
~~~
package observer;
// 显示当前天气情况
public class CurrentDisplay implements Observer, DisplayElement
{
private double temperature;
private double humidity;
private String condition;
public CurrentDisplay(Subject weatherData)
{
weatherData.registerObserver(this);
}
@Override
public void display()
{
System.out.println("Current Conditions: "+ temperature + " C degrees and "
+ humidity + "% humidity " + condition );
}
@Override
public void update(double temperature, double humidity, String condition)
{
this.temperature = temperature;
this.humidity = humidity;
this.condition = condition;
display();
}
public String toString()
{
return "This is Current Condition Display";
}
}
~~~
~~~
package observer;
// 显示历史统计数据
public class StatisticDisplay implements Observer, DisplayElement
{
private double maxTemperature = 0;
private double minTemperature = 100;
private double temperatureSum = 0;
private int totalUpdating = 0;
public StatisticDisplay(Subject weatherData)
{
weatherData.registerObserver(this);
}
@Override
public void display()
{
System.out.println("Avg/Max/Min temperture: " + temperatureSum/totalUpdating + "/"
+ maxTemperature + "/" + minTemperature);
}
@Override
public void update(double temperature, double humidity, String condition)
{
temperatureSum += temperature;
totalUpdating++;
if(temperature > maxTemperature)
{
maxTemperature = temperature;
}
if(temperature < minTemperature)
{
minTemperature = temperature;
}
display();
}
public String toString()
{
return "This is Statictic Display";
}
}
~~~
### 一个Main测试类
~~~
package observer;
public class Main
{
public static void main(String[] args)
{
WeatherData weatherStation = new WeatherData(); // 一个气象站作为Subject
CurrentDisplay currentDisplay = new CurrentDisplay(weatherStation); // 注册Observer1
System.out.println(currentDisplay); // 这个是为了不让eclipse报出warning
StatisticDisplay statisticDisplay = new StatisticDisplay(weatherStation); // 注册Observer2
System.out.println(statisticDisplay); // 这个是为了不让eclipse报出warning
weatherStation.setMeasurements(20, 30, "晴天"); // 气象站测量出了新的天气,在第一时间通知所有的观察者
weatherStation.setMeasurements(16, 50, "多云");
weatherStation.setMeasurements(12, 60, "大风");
weatherStation.setMeasurements(12, 90, "小雨");
weatherStation.setMeasurements(11, 98, "大雨");
}
}
~~~
# 运行结果
直接从eclipse复制过来
~~~
This is Current Condition Display
This is Statictic Display
Current Conditions: 20.0 C degrees and 30.0% humidity 晴天
Avg/Max/Min temperture: 20.0/20.0/20.0
Current Conditions: 16.0 C degrees and 50.0% humidity 多云
Avg/Max/Min temperture: 18.0/20.0/16.0
Current Conditions: 12.0 C degrees and 60.0% humidity 大风
Avg/Max/Min temperture: 16.0/20.0/12.0
Current Conditions: 12.0 C degrees and 90.0% humidity 小雨
Avg/Max/Min temperture: 15.0/20.0/12.0
Current Conditions: 11.0 C degrees and 98.0% humidity 大雨
Avg/Max/Min temperture: 14.2/20.0/11.0
~~~
# 总结
这个观察者模式,多个观察者(Observer)“围观”一个被观察者(Subject),被观察者是万众瞩目的焦点。观察者如果想得到Subject的“观察权”,首先需要向Subject **申请注册**,如果惹Subject生气了,不让你观察了,就把你**踢开**。Subject一旦发生了变化,就立刻**通知**所有观察者,引起观察者的**更新**。
黑体字就是Subject接口中的三个和Observer接口中的一个关键的抽象方法,必须实现:
申请注册:registerObserver()
踢开 :removeObserver()
通知 :notifyObservers()
更新 :update()
值得注意的是:update()方法传递的参数,可以传递整个subject对象,也可以只传递部分有用的数据成员。实验楼网站课程中的例子就是传递整个subject对象,《Head First Design Pattern》就是传递部分有用的数据成员。无论无何,最关键的是:
Subject对象中的数据成员发生改变
→ 触发Subject.notifyObservers()方法
→ 触发所有Observer.update() 方法。
万变离不开这个链条。
Java中有一个java.util.Observable类(被观察者,Subject);还有一个java.util.Observer接口(观察者,Obserer)。看来观察者模式很常用,以至于都写进到java类库里面去了。
[更多设计模式,在新标签页中打开这里](http://blog.csdn.net/u013390476/article/details/50333763)
';
设计模式——单例模式 Java源代码
最后更新于:2022-04-01 20:01:59
四个不同单例模式写法的Java源代码
- 懒汉模式
- 饿汉模式
- 懒汉模式 + 安全线程
- 双重检验锁
第一个懒汉模式线程不安全,后三个都是线程安全的。四者的共有特点,也是单例模式的最主要特点,是其**构造函数是私有的**。还有一个特点是,有一个**静态**的getInstance() 方法,静态方法就是类方法。
# 懒汉模式
这个写法是GoF提出单例模式时候给出的例子,影响力大,写法简单。
~~~
package singleton;
// 懒汉模式
public class SingletonLazy
{
private static SingletonLazy uniqueInstance;
private SingletonLazy(){} // 私有的构造函数
public static SingletonLazy getInstance()
{
if(uniqueInstance == null)
{
uniqueInstance = new SingletonLazy();
}
return uniqueInstance;
}
public String toString()
{
return "A simple way to apply Singleton, but is not thread safe";
}
}
~~~
然而问题在于:当有多于一个线程的时候,懒汉模式可能失效。举个例子:
1. 线程A和线程B,相隔0.1纳秒分别执行getInstance()方法,A先于B
1. 时刻”T 纳秒”, A发现 uniqueInstance==null,准备开始new SingletonLazy( )
1. 时刻”T+0.1 纳秒”, **A还没来得及new完SingletonLazy对象**,**此时B发现 uniqueInstance==null**,也准备开始new SingletonLazy( )
1. 时刻”T+0.5 纳秒”, A成功new完了SingletonLazy对象,uniqueInstance!=null
1. 时刻”T+0.6 纳秒”, B成功new完了SingletonLazy对象
可以看出A,B两个线程都new了SingletonLazy对象,懒汉模式失效。原因在于:A和B两个线程相隔非常非常短的时间分别执行getInstance(),而且new SingletonLazy对象这个过程需要花费一定的时间。
# 饿汉模式
饿汉模式写法,是一上来(类加载的时候)就给你实例一个Singleton对象,不管你此时需不需要。回顾一下懒汉模式写法,你一开始不需要Singleton对象,然后程序运行到某一时刻,第一次调用getInstance()方法,才实例一个Singleton对象。饿汉模式的写法,由JVM保证是安全的(虽然内部机制我不懂,我才刚开始学Java),不过简单想一想,Singleton类加载之前,肯定不会有线程new Singleton(),此时Singleton()的构造函数还不存在呢~
可以这么说,饿汉模式解决线程安全问题的方法是:从根子上回避这个问题。想法很好,写法很简单,不过呢要多花费一些空间(牺牲空间,换取时间,这个世界就是很难有两全其美的事情)
~~~
package singleton;
// 饿汉模式
public class SingletonEager
{
private static SingletonEager uniqueInstance = new SingletonEager(); // 在这里 new
private SingletonEager(){} // 私有的构造函数
public static SingletonEager getInstance()
{
return uniqueInstance;
}
public String toString()
{
return "Create the unique instance when the class is loaded, which is thread safe";
}
}
~~~
# 懒汉模式(线程安全)
在懒汉模式的基础上,在getInstance() 方法的声明中,增加关键词synchronized,就可以实现线程安全了。毕竟同步嘛,线程A和B即使时间相隔非常非常短,比如相隔0.1纳秒,那也是分先后呀。就因为A快上0.1纳秒,所以就”捷足先登“了,拿到了锁!B在0.1纳秒后,发现getInstance()方法上了锁,进不去了。
~~~
package singleton;
public class SingletonThreadSafe
{
private static SingletonThreadSafe uniqueInstance;
private SingletonThreadSafe(){} // 私有的构造函数
// 这里同步了
public static synchronized SingletonThreadSafe getInstance()
{
if(uniqueInstance == null)
{
uniqueInstance = new SingletonThreadSafe();
}
return uniqueInstance;
}
public String toString()
{
return "The getInstance() method is declared with keyword 'synchronized',"
+ " which is thread safe, but with low performance";
}
}
~~~
不过《Head First Design Pattern》说:Just keep in mind that synchronizing a method can decrease performance by a factor of 100。相差100倍,这对于程序性能的影响是相当的大呀!
# 双重检验锁
这个是上面的synchronized方法的升级版本。仔细想一想,只有在第一次getInstance()的时候,才需要new singleton对象,对吧?如果不是第一次getInstance(),那就说明singleton对象已经存在了~于是有了下面的优化代码
~~~
package singleton;
// double checked locking
public class SingletonDCL
{ // 注意这个关键词
private volatile static SingletonDCL uniqueInstance;
private SingletonDCL(){} // 私有的构造函数
public static SingletonDCL getInstance()
{
if(uniqueInstance == null) // check once
{
synchronized(SingletonDCL.class)
{
if(uniqueInstance == null) // check twice
{
uniqueInstance = new SingletonDCL();
}
}
}
return uniqueInstance;
}
public String toString()
{
return "A thread safe way to apply Singleton with good performance";
}
}
~~~
synchronized的不是一个方法,而是一个方法里面的一个代码块,这样被synchronized的部分减少了。
- 注意1:synchronized前后分别check两次。第一个check,Singleton是否曾经被实例化过;第二个check,就相当于上一个例子”懒汉模式+线程安全“中的check
- 注意2:volatile关键词,这涉及到JVM内部的机制,先强行记住就行了。
# 测试及补充
~~~
package singleton;
public class Main
{
public static void main(String[] args)
{
SingletonLazy singletonLazy = SingletonLazy.getInstance();
SingletonLazy singletonLazy2 = SingletonLazy.getInstance();
System.out.println(singletonLazy);
if(singletonLazy2.equals(singletonLazy))
{
System.out.println("true"); // 同一个引用
}
else
{
System.out.println("false");
}
}
}
~~~
运行结果:
~~~
A simple way to apply Singleton, but is not thread safe
true
~~~
**补充:**
《Head First Design Pattern》书中单例模式就这4种写法。不过其实还有更多写法,在实验楼网站中,就还有静态内部类写法和枚举类型写法。《Head First Design Pattern》在GitHub的代码中,有一个例子为了让单例模式能够派生出子类,把构造函数和静态数据成员声明为protected(子类访问权限)。
深入单例模式以及其他设计模式,[猛戳这里(可以在新标签页中打开~)](http://blog.csdn.net/u013390476/article/details/50333763)
[某大神博客,C++版本的单例模式,我觉得写的不错](http://blog.csdn.net/fu_zk/article/details/11892095)
[这个链接好,C++博大精深。。。](http://segmentfault.com/q/1010000000593968)
';
设计模式——抽象工厂 Java源代码
最后更新于:2022-04-01 20:01:57
# 前言
本程序改编自《Head First Design Patterns》(简称*HF* )中的关于Pizza的例子(可以在GitHub下载)。
![HF抽象工厂](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56cab10c88a10.jpg "")
图:*HF* 上的抽象工厂类图。Client位于图右上角。AbstractProductA的一个子类和AbstractProductB的一个子类,两个子类合在一起构成一个**产品族,即 a family of products**
*HF* 上面的例子的Client是一个抽象类,及其子类;然后又用了一个工厂方法模式,封装了new Client( )的过程。我觉得这样子没有必要,于是把书上的例子做了简化:把Client改成普通类;然后再用一个简单工厂封装new Client( )的过程。这样子丝毫不影响抽象工厂的精髓,反而还可以简化代码,突出重点。
PS. 实验楼网站里面的例子中,Client是一个普通类,没有用简单工厂或者工厂方法模式封装Client。我觉得这样子不好,因为这样直接把”Client的new的过程“暴露在Main里面了。
抽象工厂模式,可以和简单工厂或者工厂方法模式“组合在一起”,让后两者进一步封装抽象工厂中的Client。
# 严格的类图
总共14个java文件
- 两个抽象产品接口
- 四个具体产品类
- 一个抽象工厂接口
- 四个具体工厂类
- 一个Client类
- 一个封装Client类的简单工厂
- 一个Main类
![抽象工厂类图](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56cab10cc98f3.jpg "")
图:我绘制的类图,严格的抽象工厂模式类图(一些资料不区分聚合、关联、依赖,把这三种关系统统当作依赖关系)。我使用了一个叫做AmaterasUML 的插件。
# Talk is cheap, show me the code
### 两个抽象产品接口
~~~
package abstractFactory;
// 抽象产品A——蔬菜
public interface Veggies {
public String toString();
}
~~~
~~~
package abstractFactory;
// 抽象产品B——肉馅
public interface Meat {
public String toString();
}
~~~
### 四个具体产品类
~~~
package abstractFactory;
// 具体产品A1——白菜
public class Cabbage implements Veggies
{
public String toString()
{
return "白菜";
}
}
~~~
~~~
package abstractFactory;
// 具体产品A2——青椒
public class GreenPepper implements Veggies
{
public String toString()
{
return "青椒";
}
}
~~~
~~~
package abstractFactory;
// 具体产品B1——猪肉馅
public class Pork implements Meat {
public String toString()
{
return "猪肉";
}
}
~~~
~~~
package abstractFactory;
// 具体产品B2——牛肉馅
public class Beaf implements Meat
{
public String toString()
{
return "牛肉";
}
}
~~~
### 一个抽象工厂接口
~~~
package abstractFactory;
// 工厂接口
public interface BaoZiIngredientFactory
{
public abstract Veggies createVeggies(); // 蔬菜类产品
public abstract Meat createMeat(); // 肉类产品
}
~~~
### 四个具体工厂类
~~~
package abstractFactory;
// 具体工厂1生产的白菜猪肉包子
public class BaoZiIngredientFactory1 implements BaoZiIngredientFactory
{
@Override
public Veggies createVeggies()
{
return new Cabbage(); // A1
}
@Override
public Meat createMeat()
{
return new Pork(); // B1
}
}
~~~
~~~
package abstractFactory;
// 具体工厂2生产的白菜牛肉包子
public class BaoZiIngredientFactory2 implements BaoZiIngredientFactory
{
@Override
public Veggies createVeggies()
{
return new Cabbage(); // A1
}
@Override
public Meat createMeat()
{
return new Beaf(); // B2
}
}
~~~
~~~
package abstractFactory;
// 具体工厂3生产的青椒牛肉包子
public class BaoZiIngredientFactory3 implements BaoZiIngredientFactory
{
@Override
public Veggies createVeggies()
{
return new GreenPepper(); // A2
}
@Override
public Meat createMeat()
{
return new Beaf(); // B2
}
}
~~~
~~~
package abstractFactory;
//具体工厂4生产的青椒猪肉包子
public class BaoZiIngredientFactory4 implements BaoZiIngredientFactory
{
@Override
public Veggies createVeggies()
{
return new GreenPepper(); // A2
}
@Override
public Meat createMeat()
{
return new Pork(); // B1
}
}
~~~
### 一个Client
~~~
package abstractFactory;
// Client即包子,聚合两个抽象产品,关联工厂接口
public class BaoZi
{
String name;
// 一个包子就是一个"a family of products"
// 一个包子由分布在两类抽象产品中的三个具体产品构成
Veggies veggies; // A类产品-蔬菜馅
Meat meat; // B类产品-肉馅
BaoZiIngredientFactory factory; // 工厂接口
public BaoZi()
{
// 构造函数都不做什么事
}
public void setIngredientFactory(BaoZiIngredientFactory factory)
{
this.factory = factory;
}
public void makeBaoZi()
{
prepare(); // 准备原材料
steam(); // 蒸30分钟
setName(veggies.toString() + meat.toString() + "包子");
}
void prepare()
{
veggies = factory.createVeggies();
meat = factory.createMeat();
System.out.println("Preparing " + veggies.toString() + "," + meat.toString());
}
void steam()
{
System.out.println("Steam for 30 minutes");
}
void setName(String name)
{
this.name = name;
}
String getName()
{
return name;
}
public String toString()
{
StringBuffer result = new StringBuffer();
result.append("---- " + name + " ----\n");
if (veggies != null)
{
result.append(veggies);
result.append("馅 + ");
}
if (meat != null)
{
result.append(meat);
result.append("馅\n");
}
return result.toString();
}
}
~~~
### 一个封装Client类的简单工厂
抽象工厂可以结合简单工厂,当然也可以结合工厂方法模式,用来封装new Client( ) 的过程。简单工厂的本质是:依据传入的参数,决定生产,然后return哪一个new BaoZi(产品族, a family of products)
~~~
package abstractFactory;
public class BaoZiStore
{
protected BaoZi createBaoZi(String baoZiName)
{
BaoZi baoZi = new BaoZi();
BaoZiIngredientFactory factory = null;
if(baoZiName.equals("白菜猪肉"))
{
factory = new BaoZiIngredientFactory1();
baoZi.setIngredientFactory(factory);
}
else if(baoZiName.equals("白菜牛肉"))
{
factory = new BaoZiIngredientFactory2();
baoZi.setIngredientFactory(factory);
}
else if(baoZiName.equals("青椒牛肉"))
{
factory = new BaoZiIngredientFactory3();
baoZi.setIngredientFactory(factory);
}
else if(baoZiName.equals("青椒猪肉"))
{
factory = new BaoZiIngredientFactory4();
baoZi.setIngredientFactory(factory);
}
return baoZi;
}
public BaoZi orderBaoZi(String baoZiName)
{
BaoZi baoZi = createBaoZi(baoZiName);
baoZi.makeBaoZi();
return baoZi;
}
}
~~~
### 一个Main类
~~~
package abstractFactory;
public class Main
{
public static void main(String[] args)
{
BaoZiStore baoZiStore = new BaoZiStore();
BaoZi baoZi = null;
baoZi = baoZiStore.orderBaoZi("白菜猪肉");
System.out.println(baoZi);
baoZi = baoZiStore.orderBaoZi("白菜牛肉");
System.out.println(baoZi);
baoZi = baoZiStore.orderBaoZi("青椒牛肉");
System.out.println(baoZi);
baoZi = baoZiStore.orderBaoZi("青椒猪肉");
System.out.println(baoZi);
}
}
~~~
# 运行结果
直接从eclipse复制过来
~~~
Preparing 白菜,猪肉
Steam for 30 minutes
---- 白菜猪肉包子 ----
白菜馅 + 猪肉馅
Preparing 白菜,牛肉
Steam for 30 minutes
---- 白菜牛肉包子 ----
白菜馅 + 牛肉馅
Preparing 青椒,牛肉
Steam for 30 minutes
---- 青椒牛肉包子 ----
青椒馅 + 牛肉馅
Preparing 青椒,猪肉
Steam for 30 minutes
---- 青椒猪肉包子 ----
青椒馅 + 猪肉馅
~~~
# 抽象工厂模式和工厂方法模式的差别
**代码层面:**
工厂方法模式:工厂抽象类,几个工厂子类继承这个抽象类,工厂子类new Product() ,任务完成。
抽象工厂模式:工厂接口,几个工厂子类实现这个接口,工厂子类new 很多 Products(),但是任务还没完。
我们需要的是”a family of Products”(产品族)。此时需要引入新的角色,Client。**这个Client关联工厂子类,把工厂子类new出来的所有Products聚合起来,形成一个产品族**。然后再把这个产品族,看成”一个大产品“(我的代码中的包子)。为了让这个”大产品(包子)的new的过程“不至于直接暴露在Main类中,把这个大产品封装在一个简单工厂里面(或者*HF* 封装在工厂方法模式里面)。此时任务才完成~~
**需求层面:**
直接引用*HF* 里面的原话,原话已经不能够再简练了。。。
Abstract Factory: Remember me, Abstract Factory, and use me whenever you have families of products you need to create and you want to make sure your clients create products that belong together.
Factory Method: And I’m Factory Method; use me to decouple your client code from the concrete classes you need to instantiate, or if you know ahead of time all the concrete classes you are going to need. To use me, just subclass me implement my factory method!
深入研究设计模式,[猛戳这里(推荐新窗口打开)~](http://blog.csdn.net/u013390476/article/details/50333763)
';
设计模式——工厂方法 Java源代码
最后更新于:2022-04-01 20:01:54
本程序改编自《Head First Design Patterns》中的Pizza例子,我本学期早上经常吃包子。
总共有11个类:
- 一个工厂父类 ( Creator)
- 两个工厂子类 ( Concrete Creator)
- 一个包子父类 ( Product )
- 六个包子子类 ( Concrete Product )
- 一个Main类 ( 程序的入口 )
![工厂模式](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56cab10c0f37f.jpg "")
图:ConcreteProduct 继承 Product,ConcreteCreator 继承 Creator,ConcreateProduct 依赖 ConcreateProduct。其实,有一条线没有显示出来:Creator 依赖 Product。
*Head First* 说,这体现了依赖倒置原则:因为高层模块(Creator)依赖抽象类(Product),底层模块(ConcreteProduct)也依赖抽象类(Product)。
补充说明:我个人感觉,严格的说:ConcreteProduct 和Product 之间不是依赖,而是继承关系。勉强说的话,算作一种”非常非常强的依赖“吧。*Head First* : The ConcreteProduct class depends on the Product abstraction too, because they implement the Product interface (we’re using the “interface” in the general sense) in the Product abstraction class.
### 一个工厂父类
~~~
package factoryMethod;
public abstract class BaoZiFactory {
// 把具体的 new操作 “下放”到子类中。
abstract BaoZi createBaoZi(String baoZiName);
// 这个方法不改变,即无论包子里面的馅是什么,制作包子的流程是一样的(都是准备馅,然后蒸30分钟)
final public BaoZi makeBaoZi(String baoZiName)
{
BaoZi baoZi = createBaoZi(baoZiName);
baoZi.prepare();
baoZi.steam();
return baoZi;
}
}
~~~
### 两个工厂子类
~~~
package factoryMethod;
public class BaoZiFactoryChangsha extends BaoZiFactory {
public BaoZiFactoryChangsha()
{
System.out.println("Constructor of BaoZiFactory in Changsha\n");
}
@Override // 依据传进来的参数,决定new什么包子
BaoZi createBaoZi(String baoZiName)
{
BaoZi baoZi = null;
if(baoZiName.equals("酱肉"))
{
baoZi = new ChangshaJiangRouBaoZi();
}
else if(baoZiName.equals("青菜"))
{
baoZi = new ChangshaQingCaiBaoZi();
}
else if(baoZiName.equals("鲜肉"))
{
baoZi = new ChangshaXianRouBaoZi();
}
return baoZi;
}
}
~~~
~~~
package factoryMethod;
public class BaoZiFactoryWuhan extends BaoZiFactory {
public BaoZiFactoryWuhan()
{
System.out.println("Constructor of BaoZiFactory in Wuhan\n");
}
@Override // 依据传进来的参数,决定new什么包子
BaoZi createBaoZi(String baoZiName)
{
BaoZi baoZi = null;
if(baoZiName.equals("酱肉"))
{
baoZi = new WuhanJiangRouBaoZi();
}
else if(baoZiName.equals("青菜"))
{
baoZi = new WuhanQingCaiBaoZi();
}
else if(baoZiName.equals("鲜肉"))
{
baoZi = new WuhanXianRouBaoZi();
}
return baoZi;
}
}
~~~
### 一个包子父类
~~~
package factoryMethod;
import java.util.ArrayList;
// 父类:包子,抽象出包子共有的特性
// 有道词典:steamed stuffed bun (蒸的,填充的,小圆面包)
// 由于这三个单词加起来过长,我命名放弃采纳英文命名法,直接使用汉语拼音命名法BaoZi
public abstract class BaoZi {
private String name;
ArrayList stuffings = new ArrayList ();
public void setName(String n)
{
this.name = n;
}
public String getName()
{
return name;
}
void prepare()
{
System.out.println("Prepare " + name);
System.out.println("Stuffings are:");
for(String stuff: stuffings)
{
System.out.println(stuff);
}
}
void steam()
{
System.out.println("Steam for 30 minutes");
}
//覆盖toString (这个方法继承自java.lang.Object)
public String toString()
{
StringBuffer display = new StringBuffer();
display.append("---- " + name + " ----\n");
for(String stuff : stuffings)
{
display.append(stuff + "\n");
}
return display.toString();
}
}
~~~
### 六个包子子类
~~~
package factoryMethod;
public class ChangshaJiangRouBaoZi extends BaoZi {
public ChangshaJiangRouBaoZi()
{
setName("长沙酱肉包子");
stuffings.add("辣椒");
stuffings.add("炸酱");
stuffings.add("肉末");
stuffings.add("干子");
}
}
~~~
~~~
package factoryMethod;
public class ChangshaQingCaiBaoZi extends BaoZi {
public ChangshaQingCaiBaoZi()
{
setName("长沙青菜包子");
stuffings.add("辣椒");
stuffings.add("包菜");
stuffings.add("茄子");
}
}
~~~
~~~
package factoryMethod;
public class ChangshaXianRouBaoZi extends BaoZi {
public ChangshaXianRouBaoZi()
{
setName("长沙鲜肉包子");
stuffings.add("辣椒");
stuffings.add("鲜肉");
}
}
~~~
~~~
package factoryMethod;
public class WuhanJiangRouBaoZi extends BaoZi {
public WuhanJiangRouBaoZi()
{
setName("武汉酱肉包子");
stuffings.add("炸酱");
stuffings.add("肉末");
stuffings.add("干子");
}
}
~~~
~~~
package factoryMethod;
public class WuhanQingCaiBaoZi extends BaoZi {
public WuhanQingCaiBaoZi()
{
setName("武汉青菜包子");
stuffings.add("包菜");
stuffings.add("茄子");
}
}
~~~
~~~
package factoryMethod;
public class WuhanXianRouBaoZi extends BaoZi {
public WuhanXianRouBaoZi()
{
setName("武汉鲜肉包子");
stuffings.add("鲜肉");
}
}
~~~
### 一个Main类
~~~
package factoryMethod;
public class Main {
public static void main(String[] args)
{
BaoZiFactory wuhanFactory = new BaoZiFactoryWuhan();
BaoZiFactory changshaFactory = new BaoZiFactoryChangsha();
BaoZi baoZi = null;
baoZi = wuhanFactory.makeBaoZi("酱肉");
System.out.println("Caitao made a " + baoZi.getName() + "\n");
baoZi = wuhanFactory.makeBaoZi("青菜");
System.out.println("Caitao made a " + baoZi.getName() + "\n");
baoZi = changshaFactory.makeBaoZi("鲜肉");
System.out.println("Lucy made a " + baoZi.getName() + "\n");
baoZi = changshaFactory.makeBaoZi("青菜");
System.out.println("Lucy made a " + baoZi.getName() + "\n");
}
}
~~~
### 运行结果
直接从eclipse复制过来的
~~~
Constructor of BaoZiFactory in Wuhan
Constructor of BaoZiFactory in Changsha
Prepare 武汉酱肉包子
Stuffings are:
炸酱
肉末
干子
Steam for 30 minutes
Caitao made a 武汉酱肉包子
Prepare 武汉青菜包子
Stuffings are:
包菜
茄子
Steam for 30 minutes
Caitao made a 武汉青菜包子
Prepare 长沙鲜肉包子
Stuffings are:
辣椒
鲜肉
Steam for 30 minutes
Lucy made a 长沙鲜肉包子
Prepare 长沙青菜包子
Stuffings are:
辣椒
包菜
茄子
Steam for 30 minutes
Lucy made a 长沙青菜包子
~~~
### 如果没有工厂方法模式
又要实现同样的功能怎么破?代码如下(理想输入条件,没有异常处理)
~~~
public BaoZi makeBaoZi(String place, String type)
{
BaoZi baoZi = null;
if (place.equals("武汉"))
{
if (type.equals("酱肉"))
{
baoZi = new WuhanJiangRouBaoZi();
}
else if (type.equals("青菜"))
{
baoZi = new WuhanQingCaiBaoZi();
}
else if (type.equals("鲜肉"))
{
baoZi = new WuhanXianRouBaoZi();
}
}
else if (place.equals("长沙"))
{
if (type.equals("酱肉"))
{
baoZi = new ChangshaJiangRouBaoZi();
}
else if (type.equals("青菜"))
{
baoZi = new ChangshaQingCaiBaoZi();
}
else if (type.equals("鲜肉"))
{
baoZi = new ChangshaXianRouBaoZi();
}
}
baoZi.prepare();
baoZi.steam();
return baoZi;
}
~~~
~~~
优劣之分立马体现出来了!我们可以看到代码变短了(当然了,需要增加一些类作为“额外工作”,这是值得的)
创建包子的new操作“隐藏了”,取而代之的是一个factory对象调用createBaoZi方法
public BaoZi makeBaoZi(String place, String type)
{
BaoZiFactory factory = null;
if(place.equals("武汉"))
{
factory = new BaoZiFactoryWuhan();
}
else if(place.equals("长沙"))
{
factory = new BaoZiFactoryChangsha();
}
BaoZi baoZi = factory.createBaoZi(type);
baoZi.prepare();
baoZi.steam();
return baoZi;
}
~~~
**1. 工厂方法模式的优势**:如果位于武汉的工厂需要增加一种口味的包子(这种类似的事情经常发生),比如”热干包子“,那么工厂方法模式只用增加一个包子子类,然后修改武汉工厂子类就行了。
~~~
关键是下面的代码不变,放到哪里都一样,以不变应万变!
BaoZiFactory factory = null;
if(place.equals("武汉"))
{
factory = new BaoZiFactoryWuhan();
}
else if(place.equals("长沙"))
{
factory = new BaoZiFactoryChangsha();
}
BaoZi baoZi = factory.createBaoZi(type);
~~~
**2. 没有工厂方法模式的劣势**:如果位于武汉的工厂需要增加一种口味的包子(这种类似的事情经常发生),比如”热干包子“,那么所有用于在武汉创建包子的
if( … ) { new … }
else( ) { … }
的代码都在后面增加一个
if( 热干 ) {new ReGanBaoZiWuhan() }
else( ) { … }
~~~
BaoZi baoZi = null;
if (place.equals("武汉"))
{
if (type.equals("酱肉"))
{
baoZi = new WuhanJiangRouBaoZi();
}
else if (type.equals("青菜"))
{
baoZi = new WuhanQingCaiBaoZi();
}
else if (type.equals("鲜肉"))
{
baoZi = new WuhanXianRouBaoZi();
}
*********增加的代码*****************
else if(type.equals("热干"))
{
baoZi = new WuhanReGanBaoZi();
}
************************************
}
else if (place.equals("长沙"))
{
if (type.equals("酱肉"))
{
baoZi = new ChangshaJiangRouBaoZi();
}
else if (type.equals("青菜"))
{
baoZi = new ChangshaQingCaiBaoZi();
}
else if (type.equals("鲜肉"))
{
baoZi = new ChangshaXianRouBaoZi();
}
}
~~~
改一个地方还比较轻松,**但是关键在于很有可能在很多地方都要用到包子**。假设100个地方都要创建包子,那么100个地方的if( … )else( )“的代码都要修改!这完全是反人类,违反了开闭原则。
[更多深入分析,查看我的这一篇博客,进入目录->第三个标题](http://blog.csdn.net/u013390476/article/details/50333763)
';
面向对象程序设计进阶——设计模式 design patterns
最后更新于:2022-04-01 20:01:52
# 前言
设计模式(design pattern)是一套被反复使用、多数人知晓、经过分类编目的优秀代码设计经验的总结。关键词:重用代码、工程化、面向对象。设计模式起源于建筑设计学,最先由 [*Gang of Four*](http://baike.baidu.com/link?url=zWA56qEFuEzh6ikR326s4zGIw1ktjeXXFKMn8G-rgyp15KK72aMfW9k5VSiWW7ZhkDhpYgY0dd0_hHXDHP2pRa) 提升到了理论高度。
可复用面向对象体系分为两大系统:工具箱和框架。Java中的API属于工具箱(toolkit),Java EE (Enterprise Edition)属于框架(Framework)。设计模式是大神们在构造 Java EE 的时候的重要理论依据,学习设计模式有助于深入了解 Java EE。
我最近在[*实验楼*](https://www.shiyanlou.com/courses/100) 学习完了一门课程《Java进阶之设计模式》。截止至2015年12月16日,我已经在实验楼网站有效学习了960分钟,完整学习了5门课程。功夫在课外,我认为实验楼是一个能够开阔视野,快速入门新领域的地方。其主要特点是:课程设置广泛,内容深入浅出,提供linux环境。
GOF最早提出的设计模式总共有23个,分为三类型:创建型模式(5个),构造型模式(7个),行为型模式(11个)。后来,人们总结出了更多的设计模式,[参考wikipedia](https://en.wikipedia.org/wiki/Software_design_pattern)
- 创建型:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式
- 构造性:适配器模式、装饰模式、代理模式、组合模式、桥梁模式、外观模式、享元模式
- 行为型:模板方法模式、命令模式、责任链模式、迭代器模式、中介者模式、观察者模式、访问者模式、状态模式、解释器模式
实验楼里面讲解了其中的6个,这6个大体上是23个中使用频率最高的6个:工厂方法模式、抽象工厂模式、单例模式、适配器模式、装饰者模式、观察者模式。**我下面将首先简单介绍设计原则,然后小结一下上述常用6种模式**。我写本篇博客使用的参考资料包括:实验楼的课程、《Head First Design Patterns》、《设计模式(Java版)》、wikipedia、百度百科。
# 设计原则
-
开闭原则(OCP):open for extension, but close for modification。在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。这个是最最基础的原则。
-
单一职责原则(SRP):never be more than one reason for a class to change。专注做一件事情,仅有一个引起变化的原因,“职责”可以理解成“变化的原因”。唯有专注,才能够保证对象的高内聚;唯有单一,才能保证对象的细粒度。
-
里氏替换原则(LSP):Liskov提出:” Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S, where S is a subtype of T.” 这里的type,我理解成class。子类的对象可以无条件地替换父类的对象,并且不引起程序的改变。或者说:只要父类能出现的地方,子类就可以出现。
-
依赖倒置原则(DIP):High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions. 即高层模块不依赖低层模块;高、低层模块都依赖其抽象;抽象不依赖细节,反过来细节依赖抽象。在JAVA中,所谓的抽象,就是接口或者抽象类;所谓细节,就是就实现类(能够实例化生成对象的类)。为什么说“倒置”?因为在传统的面向过程的设计中,高层次的模块依赖于低层次的模块,抽象层依赖于具体层(这样不好),倒置过来后,就成为了面向对象设计的一个原则了(这样子好)。
-
接口隔离原则(ISP):The dependency of one class to another should depend on the smallest possible interface. 类间的依赖关系应该建立在最小的接口上面。客户端不应该依赖他不需要的接口,只提供调用者需要的方法,屏蔽不需要的方法。一个接口代表一个角色,使用多个专门的接口比使用单一的总接口更好。
-
迪米特法则(LoD)又称最少知识原则(LKP): Each unit should only talk to its friends; don’t talk to strangers. Only talk to your immediate friends. Each unit should have only limited knowledge about other units. 曾经在美国有一个项目叫做Demeter。LoD is a specific case of loose coupling.此原则的核心观念就是类之间的弱耦合,在这种情况下,类的复用率才可以提升。
# 工厂方法模式
*Head First* (简称*HF* ) 说:There is more to making objects than just using the **new** operator. Instantiation is an activity that shouldn’t always be done in public and can often lead to coupling problems. 这就是工厂方法的来源。
首先简单提一下:简单工厂。*HF* : The Simple Factory isn’t actually a Design Pattern; it’s more of a programming idiom. 代码实现:把 new操作的过程封装到一个class SimpleFactory 的一个方法中。这个方法有一个参数,这个参数决定如何new。
简单工厂进行抽象化,得到工厂方法模式。工厂方法模式**定义**:Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method let a class defer instantiation to subclasses. 简而言之**,一个工厂父类作为“总管”,管理旗下的具体工厂子类,new 操作在具体工厂子类里面**。定义中”defer”的意思是:父类不立刻进行new操作,而是交给子类进行new操作。
**代码**:[工厂方法模式例子Java源代码 (推荐在新标签页打开)](http://blog.csdn.net/u013390476/article/details/50377680)。我改编自*HF* 的pizza例子(我本学期早上流行吃包子)
**小结**:(看完代码之后再看小结)
一个工厂子类,看上去很像一个简单工厂。确实如此,不过这一些工厂子类都继承自同一个工厂父类,需要实现同一个抽象方法createBaoZi,然后利用多态性。简单工厂像是一个“一锤子买卖”,而工厂方法模式则搭建了一个框架,让工厂子类决定生产那一个具体产品。工厂方法模式更加抽象,更具有通用性,耦合度更低。
在Main类里面,**new 一个工厂子类,把其引用赋给一个工厂父类的引用**。从代码层面可以看出,当需要扩充新的工厂的时候,增加一个继承工厂父类的子类就行了。Main类里面的那个**工厂父类的引用无需改变**。更进一步说,Main类里面的所有的用到工厂父类的引用的地方都无需改变,这就是体现了开闭原则的中的“close for modification”
# 抽象工厂模式
抽象工厂模式**定义**:Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
首先需要知道一个概念“a family of products”,翻译成“产品族”。*Head First* : Factory Method provides an abstract interface for creating **one product**. Abstract Factory provides an abstract interface for creating **a family of products**.
**产品族:**假设有三类抽象产品:手机、笔记本、平板。手机分为两个具体产品:iphone 6s和华为Mate8;电脑分为两个具体产品:Macbook Pro和Surface Book;平板分为iPad Air和Surface 3。那么从手机里面二选一,从笔记本里面二选一,从平板里面二选一,总共产生了8个不同的产品族。比如”iphone 6s + Macbook Pro + iPad Air”就是8个产品族中的1个(土豪!)。
**代码**:[抽象工厂模式例子Java源代码 (推荐在新标签页打开)](http://blog.csdn.net/u013390476/article/details/50396360)
**小结:**抽象工厂模式是对工厂方法的进一步抽象。是把一组具有同一主题的单独的工厂封装起来。抽象工厂可以看成是”工厂的工厂“,抽象工厂中的一个抽象方法,很像一个工厂方法模式。一个工厂有多抽象工厂,就像是有多个工厂方法模式。
# 单例模式
有时候存在这种需求:强制要求某个类只能实例化一个对象, one and only one. 单例模式解决了这种需求,其**定义**是:Singleton Pattern ensures a class has only one instance, and provide a global point of access to it.
应用场景:
- 要求生成唯一序列号的环境
- 需要共享访问点或共享数据,例如CSDN每一篇博客都有一个计数器,用来统计阅读人数,使用单例模式保持计数机的值
- 需要创建的对象消耗大量资源,比如IO和数据库资源
- 需要定义大量的静态常量或者静态方法(工具类)的环境,比如Java基础类库中的java.lang.Runtime类
仔细想想,如果使用全局变量,则可以实现定义中的后半句,但是无法实现定义中的前半句,而且使用全局变量会污染命名空间。
**代码:**[单例模式Java源代码,可以在新标签页打开](http://blog.csdn.net/u013390476/article/details/50405825#t4)
**小结:**单例模式,顾名思义,让一个类**有且只有一个**实例对象。这样做可以达到节约或者控制系统资源的目的。在代码层面,其最主要的特征是其**构造函数是私有的**。次要特点是:数据成员Singleton的实例引用是**静态**的,而且有一个**静态**的getInstance()方法,用来负责 new Singleton() 和返回 Singleton的实例引用。
# 观察者模式
这是一个常用的行为型模式,又称为“发布者——订阅者”模式。发布者比如报社,订阅者比如老百姓。清晨爷爷奶奶们出去晨练,看见一个卖报纸的小男孩在街上大声吆喝:“今天的报纸来了!”,然后爷爷奶奶们得到通知,都去买了报纸。报社是主题(Subject),爷爷奶奶们是观察者(Observer),卖报纸的小男孩,负责告诉Observer:“Subject更新了!”
**定义**:The Observation Pattern defines a one-to-many dependency between objects so that when one objects changes state, all of its dependents are notified and updated automatically.
**代码**:[观察者模式Java代码,可以在新标签页打开](http://blog.csdn.net/u013390476/article/details/50428014)
**小结**:定义中的one就是Subject, many就是Observers;one就是发布者,many就是订阅者;one就是事件源,many就是监听者。多个观察者(Observer)“围观”一个被观察者(Subject),可以说被观察者是万众瞩目的焦点。观察者和被观察者之间是抽象耦合(类图在上面的链接里),它们属于不同的抽象化层次,非常容易扩展。使用场景是:关联行为;事件多级触发;消息队列等。
# 适配器模式
这是一个结构型模式。周边适配器到处都是,比如手机充电器就是一个把插座上面的两孔国标插座,转换成USB接口。去了美国,怎么给我的荣耀手机充电?那就那一个适配器,把美国的两孔/三孔美标插座转换成两孔国标插座。
**定义**:The Adapter Pattern converts the interface of a class into another interface the client expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
在上面的情景中,荣耀手机就是定义中的“client”;两孔国标接口就是”interface the client expect”;美国的两孔/三孔美标插座就是定义中 “converts the interface of a class”中的”interface”;Adapter 负责动词”convert”。
**代码**:[适配器模式 Java源代码,可以在新标签页中打开](http://blog.csdn.net/u013390476/article/details/50444955)
**小结**:适配器Adapter的核心,是**实现Target接口, 组合Adaptee接口**。通过实现和组合,这两种类与类之间的关系,把两个本不兼容的类(Target 和 Adaptee)联系在一起。增强了类的透明性,松耦合,提高了类的复用,增强了代码的灵活性。
# 装饰者模式
这是一个结构型模式。
很多情况下,需要扩展功能,增加职责。如果说生成子类是静态的添加额外的职责的话,那么装饰者模式则提供了一种动态的方法,较为灵活。
**定义**:attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
**代码**:[装饰者模式 Java源代码,可以在新标签页中打开](http://blog.csdn.net/u013390476/article/details/50550256)
**小结**:关键是**Decorator同时继承和组合Component**,Component是被装饰者装饰的对象。装饰者和被装饰者可以独立发展,耦合度很低,很好的符合了开闭原则,是继承关系的一种替代方案。或者说,是一种对继承关系的有力的补充,可以解决类膨胀的问题。Java.IO就是参照装饰者模式设计的。
# 一点感悟
我现在是计算机科学与技术(CS)的大三上学期。设计模式的学习,我都是利用课外时间。我学习设计模式的初衷,除了见见世面之外,就是学习Java(我大一大二都是写C++,大三上学期才学Java),可谓一箭双雕。我下载了《Head First Design Patterns》在GitHub上的样例代码, which is written in Java。我一边看书,一边看代码,抄代码,一边改编代码。理论与敲代码结合,快速提升、强化基本能力。
- 敲了不少Java代码,对Java的”感觉”加深了,代码是需要积累的
- 我在eclipse装一个插件,用来绘制UML类图。我对eclipse的”感觉”加深了,体会到了插件的强大威力
- 继承,多态。以前写C++代码,只用封装,很少用继承和多态。毕竟平时主要在写”小算法”,没有涉及到大的宏观层面的设计。在设计模式中,大量运用继承(还有实现),多态
- decouple。这个词在*HF* 中高频率出现。降低耦合,面向接口,可以增强整个程序的扩展性,便于维护,适合多团队多人合作开发
- 通过几个设计模式,慢慢体会了设计模式中的6条原则,这些原则不是”教条主义“,不能刻板遵守。而是要慢慢把这些思想融入到程序设计中
- 关于算法和设计模式。网上看到一些评论,有一个评论很生动:算法像是”单兵作战和武器装备“,设计模式像是”仗列的阵型“。算法用来解决具体问题,设计模式用来合理的把算法隔离到各个正确的地方去。真正体会设计模式,还是需要很多年的实践积累
设计模式的博客就写到这里。最近在较短时间内,集中学习了6个常用设计模式,收获颇丰。**掌握了基本思想和方法**,在以后的学习和实践中,遇到新的需要使用的模式,我就能快速学会和运用。
[一个设计模式学习网站](http://www.runoob.com/design-pattern/design-pattern-tutorial.html)
';
前言
最后更新于:2022-04-01 20:01:50
> 原文出处:[学习设计模式](http://blog.csdn.net/column/details/caitaodesignpatterns.html)
作者:[u013390476](http://blog.csdn.net/u013390476)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# 学习设计模式
> 大三学生,课余时间学习设计模式。参考资料有:参考资料包括:实验楼的课程、《Head First Design Patterns》、《设计模式(Java版)》、wikipedia、百度百科。我自认为我理解比较到位,博客也写的生动具体,代码的例子都是自己亲手敲的,请多多指教~
';