为何学习设计模式:
知道OO(面向对象)的基础概念(封装、抽象、继承、多态),不等于能够自动设计出弹性的、可复用的、可维护的系统。设计模式是人们在不断运用OO所总结出来的经验。
设计模式比库的等级更高。设计模式告诉我们如何组织类和对象以解决某种问题。
设计模式的三原则:
1. 面向接口编程,而非实现;
2. 多用组合,少用继承;
3. 高内聚、低耦合;
一、策略设计模式的定义
策略设计模式:定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
案例:Duck
二、观察者模式
观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
三、装饰者模式
装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
关键点:装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
四、工厂模式
所有的工厂模式都是用来封装对象的创建。
4.1 简单工厂模式
简单工厂其实不是一个设计模式,反而比较像是一种编程习惯。
/**
* 简单工厂
* @Date 2019-06-02
* @Author lifei
*/
public class SimplePizzaFactory {
/**
* 创建pizza
* 简单工厂, 这样做,制作比萨的代码绑定到PizzaStra中,没有弹性
* @param type
* @return
*/
public Pizza createPizza(String type){
Pizza pizza = null;
if (type.equals("cheese")){
pizza = new CheesePizza();
}else if (type.equals("greek")){
pizza = new GreekPizza();
}else if (type.equals("pepperoni")){
pizza = new PepperoniPizza();
}
return pizza;
}
}
/**
* Factory pattern
* @Date 2019-06-02
* @Author lifei
*/
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
this.factory = simplePizzaFactory;
}
/**
* 制作pizza
* @return
*/
public Pizza orderPizza(String type){
Pizza pizza =null;
// 使用简单工厂创建pizza
/**
* 简单工厂其实不是一种设计模式, 反而比较像是一种编程习惯。
*/
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
4.2 工厂方法模式
工厂方法模式(Factory Method Pattern): 定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
通过子类决定该创建的对象是什么,来达到对象创建的过程封装的目的。
4.3 抽象工厂模式
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
五、单件模式
单件模式:确保一个类只有一个实例,并提供一个全局访问点。
将构造函数私有化,然后提供一个public static修饰的方法(类方法)获取实例对象。
三种方式:
方法一:同步getInstance() 方法既简单又有效。同步一个方法可能造成程序执行效率下降100倍,因此,如果将getInstance()的程序使用在频繁运行的地方,就需要重新考虑了。
private static Singleton uniqueInstance;
public static synchronized Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
方法二:使用“急切(eagerly)”创建实例,而不用延迟实例的做法
这种做法,当应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重时使用。
jvm保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例。
public class Singleton{
// 这段代码保证了线程安全
private static Singleton uniqueInstance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return uniqueInstance;
}
}
方法三:使用“双重加锁”,在getInstance()中减少使用同步
双重加锁不适用于 1.4 及更早版本的Java!如果不能使用Java 5, 而必须使用旧版的Java,就请不要使用此技巧实现单件模式。
public class Singleton{
// volatile 关键字确保,当uniqueInstance 变量被初始化成 Singleton 实例时,多个线程正确地处理uniqueInstance 变量
private volatile static Singleton uniqueInstance;
private Singleton(){}
public static Singleton getInstance(){
if(uniqueInstance == null){
synchronized (Singleton.class){
if (uniqueInstance == null){
uniqueInstance = new Singleton();
}
}
}
}
}
六、命令模式
命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
七、适配器模式
适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
客户和被适配者是解耦的,一个不知道另一个。
应该区分“装配者模式” 和“适配器模式”。
当需要使用一个现有的类而其接口并不符合你的需求时,就使用适配器。
适配器模式有两种形式:对象适配器和类适配器。类适配器需要用到多重继承(不合适在Java中使用)。
适配器将一个对象包装起来以改变其接口;装饰者将一个对象包装起来以增加新的行为和责任;而外观将一群对象“包装”起来以简化其接口。
八、外观模式
外观模式:提供了一个统一的接口,用来访问子系统中的一群接口。外观规定了一个高层接口,让子系统更容易使用。
外观模式不只是简化了接口,也将客户从组件的子系统中解耦。
外观和适配器可以包装许多类,但是外观的意图是简化接口,二适配器的意图是将接口转换成不同接口。
当需要简化并统一一个很大的接口或一群复杂的接口时,使用外观。
九、模板方法模式
模板方法模式:在以一个方法中定义算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板方法定义了一个算法的定义,并允许子类为一个或多个步骤提供实现。
模板方法模式的抽象类可以定义具体方法、抽象方法和钩子。钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以将模板方法声明为final。
十、迭代器模式
迭代器模式:提供一种方法顺序访问一个聚合队形中的各个元素,而又不暴露其内部的表示。
“集合”(collection),指的是一群对象,其存储方式可以是各式各样的数据结构,例如:列表、数组、散列表,无论什么方式存储一律视为集合,有时候也被称为聚合(aggregate)。
迭代器分为:内部迭代器和外部迭代器。
放在gitHub上的案例: printMenu是一个内部迭代器的例子:com.hef.design08.componentmodel.Waitress#printMenu;
printVegetarianMenu是一个外部迭代器的例子:com.hef.design08.componentmodel.Waitress#printVegetarianMenu
十一、组合模式
组合模式: 允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。
迭代
十二、状态模式
状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
十三、代理模式
代理模式:为另一个对象提供一个替身或占位符以控制对这个对象的访问。
使用代理模式创建代表(representative)对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。
13.1 远程代理
(这里使用java RMI方案,未能实现)
远程代理作为另一个JVM上对象的本地代理。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。
13.2 虚拟代理
虚拟代理作为创建开销大的对象的代表。虚拟代理经常知道我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建之后,代理会将请求直接委托给对象。
实例:
1)ImageProxy首先创建一个ImageIcon,然后开始从网络URL上加载图像;
2)在加载的过程中,ImageProxy显示“CD封面加载中,请稍等。。。”;
3)当图像加载完毕,ImageProxy把所有方法调用委托给真正的ImageIcon,这些方法包括了paintIcon(),getWidth(),和getHeight();
4)如果用户请求新的图像,我们就创建新的代理,重复这样的过程。
@Override
public void paintIcon(final Component c, Graphics g, int x, int y) {
if(imageIcon!=null){
// 如果已经有icon, 就告诉它画出自己
imageIcon.paintIcon(c, g, x, y);
} else {
// 否则显示,加载中
g.drawString("CD 加载中,请稍后。。。", x + 300, y+190);
if(!retrieving){
retrieving = true;
retrievalThread = new Thread(new Runnable() {
@Override
public void run() {
imageIcon = new ImageIcon(imageURL, "CD Cover");
logger.info(imageIcon.getIconHeight() + ", " + imageIcon.getIconWidth());
c.repaint();
}
});
retrievalThread.start();
}
}
}
13.3 动态代理
代理类是在运行时创建的,我们称这个Java技术为:动态代理。
第一步: 创建InvocationHandler
InvocationHandler 实现了代理的行为。
public class NonOwnerInvocationHandler implements InvocationHandler {
private PersonBean person;
public NonOwnerInvocationHandler(PersonBean person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
try {
if (method.getName().equals("setHotOrNotRating")) {
return method.invoke(person, args);
}else if(method.getName().startsWith("set")){
throw new IllegalAccessException();
}else if(method.getName().startsWith("get")){
return method.invoke(person, args);
}
}catch (InvocationTargetException e){
e.printStackTrace();
}
return null;
}
}
第二步:写代码创建动态代理
/**
* 创建 OwnerInvocationHandler 的代理
*
* @param person
* @return
*/
public PersonBean getOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person));
}
第三步:利用适合的代理包装任何对象
十四、复合模式
复合模式在一个解决方案中结合两个或多个模式,已解决一般或重复发生的问题。