假如突然被问到“请讲讲java的23中设计模式如何实现,并述原理”。可能脑袋会突然卡壳一片空白。这就尴尬了,为了更好理解与分析这23中设计模式,亲自动手查找资料及书籍对其整理一下。
第一类有:工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
第二类有:适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
第三类有:策略模式,解释器模式,模板方法模式,观察者模式,访问者模式,中介者模式,备忘录模式,状态模式,命令模式,责任链模式,迭代子模式。
记忆口诀:解释模板策略观察访问中介,备忘状态责任命令迭代子。
另外还有并发型模式和线程池模式。
1、工厂方法模式(Factory Method)
适用于大量的产品需要创建,并且具有共同的接口。定义一个创建对象的接口,通过工厂方法决定实例化的子类。
常用的是使用简单工厂模式(Simple Factory),通过静态创建实例让我们来实现一个水果加工厂。
工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工厂产品。
抽象产品角色:它一般是具体产品继承的父类或者实现的接口。由接口或者抽象类来实现。
具体产品角色:工厂类所创建的对象就是此角色的实例,由一个具体类实现。
/**
*定义一个接口 水果加工厂
*/
public interface MakeFriut{
void getFriut();
}
/**
* 加工苹果
*/
public class AppleProcess implements MakeFriut{
@Override
public void getFriut() {
System.out.println("this is Apple Processing !");
}
}
/**
* 加工香蕉
*/
public class BananaProcess implements MakeFriut{
@Override
public void getFriut() {
System.out.println("this is Banana Processing !");
}
}
/**
* 静态工厂类实现
*/
public class FriutStaticFactory {
public static MakeFriut processApple(){
return new AppleProcess();
}
public static MakeFriut processBanana(){
return new BananaProcess();
}
}
/**
* 测试
*/
public class FactoryTest {
public static void main(String[] args) {
MakeFriut mf = FriutStaticFactory.processApple();
mf.getFriut();
}
}
可以看出,如果增加一个水果进行加工,比如加工草莓,需要实现MakeFriut接口,修改FriutStaticFractory类,添加加工草莓的方法。此种实现需要对工厂类进行修改,违背闭包原则。那么需要改进一下,就要用到工厂方法模式,创建一个工厂接口和创建多个工厂实现类。
工厂方法模式(Factory Method),基于接口创建多个工厂类:
抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
/**
*定义一个接口 水果加工厂
*/
public interface MakeFriut{
void getFriut();
}
/**
* 加工苹果
*/
public class AppleProcess implements MakeFriut{
@Override
public void getFriut() {
System.out.println("this is Apple Processing !");
}
}
/**
* 加工香蕉
*/
public class BananaProcess implements MakeFriut{
@Override
public void getFriut() {
System.out.println("this is Banana Processing !");
}
}
/**
*工厂接口
*/
public interface Provider{
MakeFriut process();
}
/**
*加工苹果
*/
public class AppleFactory implements Provider{
@Override
public MakeFriut process(){
return new AppleProcess();
}
}
/**
*加工香蕉
*/
public class BananaFactory implements Provider{
@Override
public MakeFriut process(){
return new BananaProcess();
}
}
/**
* 测试
*/
public class Test {
public static void main(String[] args) {
Provider provider = new AppleFactory();
MakeFriut mf = provider.process();
mf.getFriut();
}
}
这样每当需要加工新的水果时,只需要做一个类实现MakeFriut接口,在做一个工厂类实现Provider接口。无需改动代码,提高拓展性,符合"开放-封闭"原则。
优点:利用java的多态来实现,降低代码的耦合性,缺点:每增加一个产品,相应的也要增加一个子工厂,加大了额外的开发量。
应用场景:适用于业务简单的情况下或者具体产品很少增加的情况。
2、抽象工厂模式(Abstract Factory Pattern)
抽象工厂模式的起源或者最早的应用,是用于创建分属于不同操作系统的视窗构建。
基于接口进行编程,为创建一组或相互依赖的对象提供一个接口,无需指定其实现类。多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。每个具体工厂类可以创建多个具体产品类的实例。
抽象工厂模式的各个角色和工厂方法模式的角色相同,如下:
抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
与工厂方法模式主要区别:工厂方法模式只有一个抽象产品类,而抽象工厂可以有多个;工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂可以创建多个;相同点:一个抽象工厂类,可以派生出多个具体工厂实现类。
产品族: 位于不同产品等级结构中,功能相关联的产品组成的家族
使用抽象工厂模式可以解决创建具有相同(相似)等级结构中的多个产品族的产品对象。简单来说,就是不同汽车品牌的厂商生产汽车的模型,一辆汽车都要有发动机、底盘等然后组装而成,生产宝马的和生产奔驰的工厂,各家生产的又不能混用,只能各自组装各自的最后贴上各自的车标。
/**
*创建一个工厂的接口,用于基础工厂的实现
*/
public interface AbstractFactory {
Engine createEngine();
Chassis createChassis();
}
/**
* 汽车发动机接口
*/
public interface Engine{
//发动
void toRun();
}
/**
* 汽车底盘接口
*/
public interface Chassis {
//底盘大小
void size();
}
/**
*宝马发动机
*/
public class BMWEngine implements Engine{
private String name = "BMW Engine";
@Override
public void toRun(){
System.out.println(this.name)
}
}
/**
* 奔驰发动机
*/
public class BenzEngine implements Engine {
private String name = "BENZ Engine";
@Override
public void toRun(){
System.out.println(this.name)
}
}
/**
*宝马底盘
*/
public class BMWChassis implements Chassis {
private String name = "BMW Chassis ";
@Override
public void size(){
System.out.println(this.name)
}
}
/**
* 奔驰底盘
*/
public class BenzChassis implements Chassis {
private String name = "BENZ Chassis ";
@Override
public void size(){
System.out.println(this.name)
}
}
/**
* 宝马生产工厂
*/
public class BMWFactory implemets AbstractFactory {
@Override
public Engine createEngine() {
// TODO Auto-generated method stub
return new BMWEngine();
}
@Override
public Chassis createChassis() {
// TODO Auto-generated method stub
return new BMWChassis();
}
}
/**
* 奔驰生产工厂
*/
public class BENZFactory implemets AbstractFactory {
@Override
public Engine createEngine() {
// TODO Auto-generated method stub
return new BenzEngine();
}
@Override
public Chassis createChassis() {
// TODO Auto-generated method stub
return new BenzChassis();
}
}
/**
* 消费者买车
*/
public class Customer {
public void wantOne(AbstractFactory af){
Engine en = af.createEngine();
en.toRun();
Chassis ch = af.createChassis();
ch.size();
}
}
/**
* 测试使用
*/
public class Client {
public static void main(String[]args){
//创建消费者对象
Customer cf = new Customer();
//消费着选择并创建需要使用的汽车对象
AbstractFactory af = new BMWFactory();
//消费者获取宝马车
cf.wantOne(af);
}
}
注意:抽象工厂内的对象是相互依赖的,它们根据相关关系组合成的产品。这个产品对象被称作产品族。
优点:可以快速切换产品族,解耦。缺点:不容易扩展新的产品。
主要应用场景:系统只消费某一族的产品。和属于同一个产品族的产品一起使用。
3. 单例模式(Singleton Pattern)
单例模式主要是保证一个对象的实例化只执行一次。能够保证某一类型对象在系统中的唯一性,即某类在系统中只有一个实例。
懒汉式单例:当程序第一次访问该实例时才进行创建。
/**
* 懒汉式
*懒汉模式在使用时,容易引起不同步问题,所以应该创建同步"锁",synchronized
*/
public class LazySingleton {
private static LazySingleton instance = null;
/**
* 私有默认构造方法
*/
private LazySingleton(){}
/**
* 静态工厂方法
*/
public static synchronized LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
双重检查加锁,保证线程安全。但此种方法运行效率不高。
/**
*双重加锁
*/
public class Singleton {
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized (Singleton.class) {
//再次检查实例是否存在,如果不存在才真正的创建实例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
饿汉式单例:在程序启动或该类被加载的时候,该实例就已经被创建。
/**
*饿汉式
*/
public class EagerSingleton {
private static EagerSingleton instance = new EagerSingleton();
/**
* 私有默认构造方法
*/
private EagerSingleton(){}
/**
* 静态工厂方法
*/
public static EagerSingleton getInstance(){
return instance;
}
}
4.建造者模式(Builder Pattern)
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式中主要有建造者和导演者两个角色。
抽象建造者(Builder)角色:给 出一个抽象接口,以规范产品对象的各个组成成分的建造。
具体建造者(ConcreteBuilder)角色:担任这个角色的是与应用程序紧密相关的一些类,它们在应用程序调用下创建产品的实例。
导演者(Director)角色:担任这个角色的类调用具体建造者角色以创建产品对象。
产品(Product)角色:产品便是建造中的复杂对象。
我们用具体的代码进行分析如何创建一辆汽车。
/**
* 一辆车对象
* 代表:产品(Product)角色
*/
public class Car{
private String title;//车的品牌
private String type;//车的类型
private String color;//车的颜色
private int sits;//车的座位数
//省略get/set方法
...
}
/**
* 定义一个车的建造接口
* 代表:抽象建造者(Builder)角色
*/
public interface ICarBuild{
void buildTitle();
void buildType();
void buildColor();
void buildSits();
Car createCar();
}
/**
* 创建一辆车
* 代表:具体建造者(ConcreteBuilder)角色
*/
public class OneCarBuild implements ICarBuild{
Car car = new Car();
@Override
public void buildTitle(){
car.setTitle("宝马");
}
@Override
public void buildType(){
car.setType("SUV");
}
@Override
public void buildColor(){
car.setColor("黑色");
}
@Override
public void buildSits(){
car.setSits(5);
}
@Override
public Car createCar() {
return car;
}
}
/**
* 执行建造
* 代表:导演者(Director)角色
*/
public class Director {
public Car createCarByDirecotr(ICarBuild cb){
cb.buildTitle();
cb.buildType();
cb.buildColor();
cb.buildSits();
return cb.createCar();
}
}
/**
* 测试
*/
public class BuilderTest {
public static void main(String[] args){
Director director = new Director();
Car car = director.createCarByDirecotr(new OneCarBuild());
System.out.println(car.getTitle());
System.out.println(car.getType());
System.out.println(car.getColor());
System.out.println(car.getSits());
}
}
优点:产品的建造和表示分离,达到分离模块功能,实现了解耦。缺点:当建造者过多时,会产生很多类,难以维护。
使用场景:
1. 需要生成的产品对象有复杂的内部结构,每一个内部成分本身可以是对象,也可以仅仅是一个对象(即产品对象)的一个组成部分。2. 需要生成的产品对象的属性相互依赖。建造模式可以强制实行一种分步骤进行的建造过程,因此,如果产品对象的一个属性必须在另一个属性被赋值之后才可以被赋值,使用建造模式是一个很好的设计思想。3. 在对象创建过程中会使用到系统中的其他一些对象,这些对象在产品对象的创建过程中不易得到。
5. 原型模式(Protype Pattern)
原型模式就是讲一个对象作为原型,使用clone()方法来创建新的实例。要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。其目的就是利用一个原型对象来指明我们所要创建对象的类型,然后通过复制这个对象的方法来获得与该对象一模一样的对象实例。
简单形式的原型模式,包含的三个角色:
Prototype:抽象原型类。声明克隆自身的接口。
ConcretePrototype:具体原型类。实现克隆的具体操作。
Client:客户类。让一个原型克隆自身,从而获得一个新的对象。
/**
* 定义原型接口
* 代表:Prototype
*/
public interface Prototype{
/**
* 克隆自身的方法
* @return 一个从自身克隆出来的对象
*/
public Object clone();
}
/**
* 具体实现类
* 代表:ConcretePrototype
*/
public class ConcretePrototype implements Prototype {
private String data;
public void setData(String data){
this.data = data;
}
public String getData(){
return data;
}
@Override
public Object clone(){
//最简单的克隆,新建一个自身对象
Prototype prototype = new ConcretePrototype();
prototype.setData(this.data);
return prototype;
}
}
/**
* 具体实现类
* 代表:Client
*/
public class Client{
/**
* 持有需要使用的原型接口对象
*/
private Prototype prototype;
/**
* 构造方法,传入需要使用的原型接口对象
*/
public Client(Prototype prototype){
this.prototype = prototype;
}
public void operation(Prototype example){
//需要创建原型接口的对象
Prototype copyPrototype = prototype.clone();
}
}
优点:
1、如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
2、可以使用深克隆保持对象的状态。
3、原型模式提供了简化的创建结构。
缺点:
1、在实现深克隆的时候可能需要比较复杂的代码。
2、需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
使用场景:
1、如果创建新对象成本较大,我们可以利用已有的对象进行复制来获得。
2、如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。
3、需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
补充:这里讲两个概念:浅拷贝和深拷贝。
浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。
深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。