设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。23种设计模式分为三类:创建型模式、结构型模式和行为型模式。
创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)、访问者模式。
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。
只需要三步就可以实现单例模式
(1) 不允许其他程序用new对象 - 私有化该类的构造函数
(2) 在该类中创建对象 - 通过new在本类中创建一个本类对象
(3) 对外提供一个可以让其他程序获取该对象的方法 - 定义一个公有的方法,将在该类中所创建的对象返回
其中实现单例模式有两种方式:饿汉式和懒汉式。
饿汉式的实现比较简单,在类加载的时候就完成了实例化,避免了线程的同步问题。由于在类加载的时候就实例化了,所以没有达到Lazy Loading(懒加载)的效果,也就是说可能我没有用到这个实例,但是它也会加载,会造成内存的浪费(一般此浪费可以忽略,所以推荐使用此方式)。
package angelia.pattern.singleton;
public class EHanSingleton {
private EHanSingleton() {}
private static EHanSingleton eHanSingleton = new EHanSingleton();
public static EHanSingleton getInstance() {
if (eHanSingleton == null) {
eHanSingleton = new EHanSingleton();
}
return eHanSingleton;
}
}
懒汉式达到Lazy Loading(懒加载)的效果。
package angelia.pattern.singleton;
public class LanHanSingleton {
private static LanHanSingleton lanHanSingleton;
private LanHanSingleton() {}
public static LanHanSingleton getLanHanSingleton() {
if (lanHanSingleton == null) {
synchronized (LanHanSingleton.class) {
if (lanHanSingleton == null) {
lanHanSingleton = new LanHanSingleton();
}
}
}
return lanHanSingleton;
}
}
内部类实现懒汉式,这种方式跟饿汉式方式采用的机制类似,但又有不同。比如都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的是,饿汉式方式是只要Singleton类被装载就会实例化, 内部类是在需要实例化时,调用getInstance方法,才会装载SingletonInnerClass类。这样即避免了线程不安全,延迟加载,效率又高。
package angelia.pattern.singleton;
public class LanHanSingletonIC {
private LanHanSingletonIC() {}
private static class SingletonInnerClass {
private static LanHanSingletonIC lanHanSingletonIC = new LanHanSingletonIC();
}
public static LanHanSingletonIC getInstance() {
return SingletonInnerClass.lanHanSingletonIC;
}
}
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
工厂模式又分为:简单工厂模式、工厂方法模式和抽象工厂模式。
简单工厂模式,通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
本媛是个吃货,做梦都想有一个自己的饭店,每天码完代码都能喝到一种鲜美的排骨汤。
首先我要有各种口味的排骨汤:
package angelia.pattern.factory;
public abstract class PaiGuTang {
protected String name;
public void prepare() {
System.out.println("准备各种食材。。。");
}
public void cooking() {
System.out.println("开始做" + this.name);
}
}
public class DongGuaPaiGuTang extends PaiGuTang {
public DongGuaPaiGuTang() {
this.name = "冬瓜排骨汤";
}
}
public class LuoBoPaiGuTang extends PaiGuTang {
public LuoBoPaiGuTang() {
this.name = "萝卜排骨汤";
}
}
public class YuMiPaiGuTang extends PaiGuTang {
public YuMiPaiGuTang() {
this.name = "玉米排骨汤";
}
}
使用简单工厂之前的排骨汤店
public class PaiGuTangOldStore {
public PaiGuTang sellPaiGuTang(String type) {
PaiGuTang paiGuTang = null;
if (type.equals("DongGua")) {
paiGuTang = new DongGuaPaiGuTang();
} else if (type.equals("LuoBo")) {
paiGuTang = new LuoBoPaiGuTang();
} else if (type.equals("YuMi")) {
paiGuTang = new YuMiPaiGuTang();
}
paiGuTang.prepare();
paiGuTang.cooking();
return paiGuTang;
}
}
创建简单工厂
public class SimplePaiGuTangFactroy {
public PaiGuTang createPaiGuTang(String type) {
PaiGuTang paiGuTang = null;
if (type.equals("DongGua")) {
paiGuTang = new DongGuaPaiGuTang();
} else if (type.equals("LuoBo")) {
paiGuTang = new LuoBoPaiGuTang();
} else if (type.equals("YuMi")) {
paiGuTang = new YuMiPaiGuTang();
}
return paiGuTang;
}
}
使用简单工厂之后的排骨汤店(组合的方式)
public class PaiGuTangStore {
private SimplePaiGuTangFactroy factroy;
public PaiGuTangStore(SimplePaiGuTangFactroy factroy) {
this.factroy = factroy;
}
public PaiGuTang sellPaiGuTang(String type) {
PaiGuTang paiGuTang = factroy.createPaiGuTang(type);
paiGuTang.prepare();
paiGuTang.cooking();
return paiGuTang;
}
}
用了简单工厂模式,无论以后我想添加什么汤(海带排骨汤,莲藕炖排骨,土豆排骨汤,茶树菇排骨汤,山药排骨汤,现在好想喝汤),或者是删除什么种类的汤都和Store,Store只负责卖汤。
工厂方法模式,定义一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法模式把类实例化的过程推迟到子类。
由于程序员经常出差外地,所以决定去上海开个分店,去北京开个分店。有了分店,那总店就可以抽象了。
总店
public abstract class PaiGuTangStore {
public abstract PaiGuTang createPaiGuTang(String type);
public PaiGuTang sellPaiGuTang(String type) {
PaiGuTang paiGuTang = createPaiGuTang(type);
paiGuTang.prepare();
paiGuTang.cooking();
return paiGuTang;
}
}
上海分店
public class ShangHaiPaiGuTangStore extends PaiGuTangStore {
@Override
public PaiGuTang createPaiGuTang(String type) {
PaiGuTang paiGuTang = null;
if (type.equals("DongGua")) {
paiGuTang = new DongGuaPaiGuTang();
} else if (type.equals("YuMi")) {
paiGuTang = new YuMiPaiGuTang();
}
return paiGuTang;
}
}
北京分店
public class BeiJingPaiGuTangStore extends PaiGuTangStore {
@Override
public PaiGuTang createPaiGuTang(String type) {
PaiGuTang paiGuTang = null;
if (type.equals("DongGua")) {
paiGuTang = new DongGuaPaiGuTang();
} else if (type.equals("LuoBo")) {
paiGuTang = new LuoBoPaiGuTang();
}
return paiGuTang;
}
}
通过例子看出我们把制排骨汤的过程以抽象方法的形式让子类去决定了。
抽象工厂模式,提供一个接口,用于创建相关的或依赖对象的家族,而不需要明确指定具体类。代码略。
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。JDK中有很多地方实现了观察者模式,比如XXXView.addXXXListenter 。
下面结合JDK自带的观察者模式举一个例子。
场景:博客上提供了订阅按钮,当读者点击订阅该文章,后续该文章有更新,就会通知订阅此文章的读者。
Subject --> java.util.Observable(类)
public class Observable {
private boolean changed = false; // 是否被改变
private Vector obs; // Vector集合用来装观察者对象,注意Vector是线程安全的
public Observable() {
obs = new Vector<>();
}
// 如果Vector集合中已有的观察者不包含此观察者,则向观察者集中添加此观察者。
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
// 调用vector的removeElement方法把观察者移除出去
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
// 通知所有的观察者更新
public void notifyObservers() {
notifyObservers(null);
}
// 如果hasChanged方法指示对象已改变,则通知其所有观察者,并调用clearChanged方法来指示此对象不再改变。
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
// 从对象的观察者集合中删除某个观察者。
public synchronized void deleteObservers() {
obs.removeAllElements();
}
// 标记此Observable对象为已改变的对象,hasChanged方法将返回true。
protected synchronized void setChanged() {
changed = true;
}
// 指示对象不再改变,或它已对其所有观察者通知了最近的改变,所以hasChanged方法将返回false。
protected synchronized void clearChanged() {
changed = false;
}
// 判断对象是否改变。
public synchronized boolean hasChanged() {
return changed;
}
// 返回Observable对象的观察者数目。
public synchronized int countObservers() {
return obs.size();
}
}
Observer --> java.util.Observer(接口)
public interface Observer {
// 只要改变了observable对象就调用此方法。
void update(Observable o, Object arg);
}
被观察者
public class BlogObservable extends Observable {
private String name;
private String content;
public BlogObservable(String name) {
super();
this.name = name;
}
public void registerReader(Observer observer) {
if (observer instanceof Reader) {
Reader reader = (Reader) observer;
addObserver(reader);
System.out.println("欢迎您" + reader.getName() + "关注" + getName());
}
}
public void cancelReader(Observer observer) {
if (observer instanceof Reader) {
Reader reader = (Reader) observer;
deleteObserver(reader);
System.out.println(reader.getName() + "已取消" + getName() + "订阅");
}
}
public void notifyReaders() {
setContent("观察者模式");
notifyObservers(getContent());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.setChanged();
this.content = content;
}
}
观察者
public class Reader implements Observer {
private String name;
public Reader(String name) {
super();
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
String content = (String) arg;// 文章章节数
if (o instanceof BlogObservable) {
BlogObservable blog = (BlogObservable) o;
System.out.println(getName() + "您好," + blog.getName() + "已更新到" + content);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试类
public class ObserverPatternDemo {
public static void main(String[] args) {
Reader reader1 = new Reader("Angelia");
Reader reader2 = new Reader("Snowdrop");
Reader reader3 = new Reader("Coco");
BlogObservable blog = new BlogObservable("23种设计模式常用模式总结文章");
blog.registerReader(reader1);
blog.registerReader(reader2);
blog.registerReader(reader3);
blog.cancelReader(reader3);
blog.notifyReaders();
}
}
此例子是基于jdk自带的观察者模式,不过Observable是一个类,而不是一个接口,导致Observable类的扩展性不高,不如自己实现的观察者模式灵活。Observable将某些方法保护了起来(setChanged()和clearChanged()为protected),这意味着除非继承自Observable,否则将有关键的方法不能调用。导致无法通过组合的方式使其它类获得Observable类的功能。
观察者模式是一个使用非常频繁的模式,在基于事件模型中,事件源发生变化,那么会通知事件影响者发生也发生变化。
策略模式定义了算法族,分别封装起来,让它们之间可相互替换,此模式让算法的变化独立于使用算法的客户。
统一接口
public interface Strategy {
public int doOperation(int num1, int num2);
}
三个实现类
public class AddOperation implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class SubOperation implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
public class MulOperation implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
创建一个使用了某种策略的类Context
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
测试类
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new AddOperation());
System.out.println("6 + 5 = " + context.executeStrategy(6, 5));
context = new Context(new SubOperation());
System.out.println("6 - 5 = " + context.executeStrategy(6, 5));
context = new Context(new MulOperation());
System.out.println("6 * 5 = " + context.executeStrategy(6, 5));
}
}
策略模式避免使用多重条件判断,扩展性良好。不过随着策略类增多,会出现策略类膨胀的问题,而且所有策略类都需要对外暴露。
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。将一个类的接口转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以相互合作。
适配器模式主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
类适配器模式,通过继承来实现适配器功能。
比如我有一个Micro USB手机充电器接口, 但是现在我想给我的Type-c插口的手机充电,怎么办?只能准备一个可以把Micro USB接口转换成Type-c接口的转换器。
MicroUSB接口
public interface MicroUSBI {
public void isMicroUSB();
}
TypeC接口
public interface TypeCI {
public void isTypeC();
}
TypeC接口实现类
public class TypeC implements TypeCI {
@Override
public void isTypeC() {
System.out.println("This is TypeC interface");
}
}
适配器
public class ChargerAdapter extends TypeC implements MicroUSBI {
@Override
public void isMicroUSB() {
isTypeC();
}
}
测试类
public class AdapterTest {
public static void main(String[] args) {
MicroUSBI usb = new ChargerAdapter();
usb.isMicroUSB();
}
}
对象适配器模式,通过组合来实现适配器功能。
public class ChargerAdapter implements MicroUSBI {
private TypeCI typeC;
public ChargerAdapter(TypeCI typeC) {
super();
this.typeC = typeC;
}
@Override
public void isMicroUSB() {
typeC.isTypeC();
}
}
接口适配器模式,通过抽象类来实现适配,这种适配稍别于上面所述的适配。
当一个接口定义了N多个方法,而现在只想使用其中的一个到几个方法,如果直接实现接口,那么要对所有的方法进行实现,哪怕仅仅是对不需要的方法进行置空也会导致这个类变得臃肿,调用也不方便,这时可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空。在创建抽象类的继承类,只需要使用的那几个方法即可。
目标接口
public interface MyInterface {
void methodA();
void methodB();
void methodC();
void methodD();
}
适配器
public class Adapter implements MyInterface {
@Override
public void methodA() {
}
@Override
public void methodB() {
}
@Override
public void methodC() {
}
@Override
public void methodD() {
}
}
实现类
public class ABImp extends Adapter {
@Override
public void methodA() {
System.out.println("方法A被调用");
}
@Override
public void methodB() {
System.out.println("方法B被调用");
}
}
测试类
public class AdapterTest {
public static void main(String[] args) {
MyInterface in = new ABImp();
in.methodA();
in.methodB();
}
}
命令模式(Command Pattern)是一种数据驱动的设计模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
对于上面的解释不是很懂,没关系,下面来个例子。现在智能家电很是火热,比如电脑,电视、扫地机器人、空气净化器等家电。生活方便的的同时,有时也很烦恼,家里遥控器一大堆,不过自从有了小爱同学,妈妈再也不用担心我的遥控器。哈哈~小爱同学像个总遥控器控制所有家电(小米)的开关,然后每给小爱同学发出一个命令,她可以准确应答。然后随着可以发展,家电只会越来越多,越来越个性化,所以即要求小爱对应功能个性化,又要有非常强的扩展性。
首先列举下小爱同学支持家电(因为大部分代码重复,只简单列举电视,电脑两种家电)。
public class Television {
public void on() {
System.out.println("小爱同学打开电视...");
}
public void off() {
System.out.println("小爱同学关闭电视...");
}
}
public class Computer {
public void on() {
System.out.println("小爱同学打开电脑...");
}
public void off() {
System.out.println("小爱同学关闭电脑...");
}
}
封装命令为类
public interface Command {
public void execute();
}
public class TelevisionOnCommand implements Command {
private Television tv;
public TelevisionOnCommand(Television tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.on();
}
}
public class TelevisionOffCommand implements Command {
private Television tv;
public TelevisionOffCommand(Television tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.off();
}
}
public class ComputerOnCommand implements Command {
private Computer computer;
public ComputerOnCommand(Computer computer) {
this.computer = computer;
}
@Override
public void execute() {
computer.on();
}
}
public class ComputerOffCommand implements Command {
private Computer computer;
public ComputerOffCommand(Computer computer) {
this.computer = computer;
}
@Override
public void execute() {
computer.off();
}
}
把所有命令封装成类后,看小爱同学实现(只是举个例子,有不恰当之处望指正)
public class XiaoAiMenu {
private static final int MENU_SIZE = 10;
private Command[] commands;
public XiaoAiMenu() {
commands = new Command[MENU_SIZE];
for (int i = 0; i < MENU_SIZE; i++) {
commands[i] = new NoCommand();
}
}
/**
* 设置对应的命令
* @param index
* @param command
*/
public void setCommand(int index, Command command) {
commands[index] = command;
}
/**
* 模拟触发对应的命令
* @param index
*/
public void triggerCommand(int index) {
commands[index].execute();
}
}
public class NoCommand implements Command {
@Override
public void execute() {}
}
上面每下达一个指令,只能触发一个命令。美中不足的是,每天早上起来后,想打开电视看新闻,打开电脑处理邮件,需要一连下达几个指令。现在我想要一个指令,可以触发一系列命令。
public class Commands implements Command {
private Command[] commands;
public Commands(Command ...commands) {
super();
this.commands = commands;
}
@Override
public void execute() {
for (int i = 0; i < commands.length; i++) {
commands[i].execute();
}
}
}
测试
public class CommandTest {
public static void main(String[] args) {
Television tv = new Television();
Computer computer = new Computer();
XiaoAiMenu menu = new XiaoAiMenu();
// 为每个按钮设置功能
menu.setCommand(0, new TelevisionOnCommand(tv));
menu.setCommand(1, new TelevisionOffCommand(tv));
menu.setCommand(2, new ComputerOnCommand(computer));
menu.setCommand(3, new ComputerOffCommand(computer));
// 触发命令
menu.triggerCommand(0);
menu.triggerCommand(1);
menu.triggerCommand(2);
menu.triggerCommand(3);
menu.triggerCommand(5);// 这个没有指定,但是不会出任何问题,NoCommand的功劳
Commands commands = new Commands(new TelevisionOnCommand(tv), new ComputerOnCommand(computer));
menu.setCommand(6, commands);
menu.triggerCommand(6);
}
}
小爱同学打开电视...
小爱同学关闭电视...
小爱同学打开电脑...
小爱同学关闭电脑...
***早上好,请打开家电们***
小爱同学打开电视...
小爱同学打开电脑...