1、设计模式七大原则
1.1 单一职责
对类来说,一个类只负责一个职责;如果一个类A负责两个职责,那么更改职责1时可能会影响职责2,所以必须将类A分为两个类。
作用:
- 降低类的复杂度
- 提高类的可读性
- 降低变更引起的风险
问题:
public class Test1 {public static void main(String[] args) {vehicle vehicle = new vehicle();vehicle.run("汽车");vehicle.run("摩托车");vehicle.run("飞机");}//该类就违反了单一职责问题,天上的交通工具和陆地的相混合static class vehicle{public void run(String vehicle){System.out.println(vehicle+"在公路上运行....");}}}
解决方案一:
public class Test2 {/*虽然遵守了单一职责原则,但这样开销会很大,需要将类分解*/public static void main(String[] args) {roadVehicle roadVehicle = new roadVehicle();airVehicle airVehicle = new airVehicle();roadVehicle.run("汽车");roadVehicle.run("摩托车");airVehicle.run("飞机");}static class roadVehicle{public void run(String vehicle){System.out.println(vehicle+"在公路上运行....");}}static class airVehicle{public void run(String vehicle){System.out.println(vehicle+"在天空运行....");}}}
解决方案二:
public class Test3 {/*这种修改没有分解类,只是增加方法类级别上没有遵守单一职责,但是方法上遵守了单一职责原则*/public static void main(String[] args) {Vehicle2 vehicle2 = new Vehicle2();vehicle2.run("汽车");vehicle2.run("摩托车");vehicle2.fly("飞机");}static class Vehicle2{public void run(String vehicle){System.out.println(vehicle+"在公路上运行....");}public void fly(String vehicle){System.out.println(vehicle+"在天空上运行....");}}}
1.2 接口隔离
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上
问题:
这个图的意思是:类“犬科”依赖接口I中的方法:捕食(),行走(),奔跑(); 类“鸟类”依赖接口I中的方法捕食(),滑翔(),飞翔(); 宠物狗类与鸽子类分别是对类“犬科”与类“鸟类”依赖的实现。 对于具体的类:宠物狗与鸽子来说,虽然他们都存在着用不到的方法,但由于实现了接口1,所以也 必须要实现这些用不到的方法。
这样会导致接口过于臃肿,只要接口中出现的方法,不管对依赖于它们的类有没有用处,实现类中都必须去实现这些方法,这显然是不好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进拆分。在这里我们将原有的接口I拆分为三个接口,拆分后的设计如下所示:
改进:
1.3 依赖倒转
- 高层模块不应该依赖底层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转的中心思想是面向对象编程
- 使用接口或抽象类的目的是指定规范,不设计任何具体的操作,把展现细节的任务交给他们的实现类去做。
问题:
public class Test1 {public static void main(String[] args) {Person person = new Person();person.receive(new Email());person.receiveWeiXin(new WeiXin());}//Person接收消息/*方式一实现这样虽然简单,容易实现但是如果接收的信息不是电子邮件,是微信、QQ等,则就需要在添加类,Person中也要再添加对应的方法*/static class Person{public void receive(Email email){System.out.println(email.getInfo());}//新增微信功能,需要新增方法 使代码冗余public void receiveWeiXin(WeiXin weiXin){System.out.println(weiXin.getInfo());}}static class Email{public String getInfo(){return "电子邮件信息:hello";}}static class WeiXin{public String getInfo(){return "微信消息来了!";}}}
改进:
//依赖关系的传递方式//1.接口传递 2.构造方法传递 3.setter方法传递public class Test2 {public static void main(String[] args) {Person person = new Person();person.receive(new Email2());person.receive(new WeiXin());}/*方式二:通过接口传递实现依赖,每次改动就不需要改变Person类的方法,只需要增加接口的实现类即可*/static class Person{public void receive(IReceiver receiver){System.out.println(receiver.getInfo());}}//自定义接口interface IReceiver{public String getInfo();}static class Email2 implements IReceiver{public String getInfo(){return "电子邮件信息:hello";}}static class WeiXin implements IReceiver{@Overridepublic String getInfo() {return "微信消息来了!";}}}
1.4 里氏替换
原则:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
问题:
/*几维鸟类在继承父类时,无意间重写了父类的方法setSpeed,这违背了里氏替换原则,致使flySpeed等于0了,导致发生错误*/运行结果:无法计算出几维鸟的飞行速度如果飞行300公里:燕子将飞行2.5小时.几维鸟将飞行Infinity小时。
public class Test1 {public static void main(String[] args) {Bird bird1 = new Swallow();Bird bird2 = new BrownKiwi();bird1.setSpeed(120);bird2.setSpeed(120);System.out.println("如果飞行300公里:");try {System.out.println("燕子将飞行" + bird1.getFlyTime(300) + "小时.");System.out.println("几维鸟将飞行" + bird2.getFlyTime(300) + "小时。");} catch (Exception err) {System.out.println("发生错误了!");}}}//鸟类 燕子类和几维鸟类都继承了该父类class Bird {double flySpeed;public void setSpeed(double speed) {flySpeed = speed;}public double getFlyTime(double distance) {return (distance / flySpeed);}}//燕子类class Swallow extends Bird {}//几维鸟类class BrownKiwi extends Bird {//重写了父类的方法public void setSpeed(double speed) {flySpeed = 0;}}
解决方法:
让原有的子类和父类都继承一个更通俗的基类;如取消几维鸟原来的继承关系,定义鸟类和几维鸟的父类,如动物类,它们都有奔跑的能力。几维鸟的飞行速度虽然为 0,但奔跑速度不为 0,可以计算出其奔跑 300 千米所要花费的时间。
1.5 开闭原则
- 最基础、最重要的设计原则;对扩展开放(提供方),对修改关闭(使用方)
- 当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求
- 编程中开闭原则是面向对象程序设计的终极目标
问题:
public class Test1 {public static void main(String[] args) {GraphEditor graphEditor=new GraphEditor();graphEditor.drawShape(new Rectangle());graphEditor.drawShape(new Circle());}/*该方式实现虽然比较好理解,操作简单,但是违反了ocp原则,如果需要增加一个绘三角形的业务,那么不仅需要增加三角形子类,还会对绘图的类(使用方)进行修改,代码复杂*///绘图的类(使用方)static class GraphEditor{public void drawShape(Shape shape){if(shape.m_type==1){drawRectangle();}if(shape.m_type==2){drawCircle();}}private void drawCircle() {System.out.println("绘制圆形");}private void drawRectangle() {System.out.println("绘制矩形");}}//Shape类 基类static class Shape{int m_type;}//矩形子类static class Rectangle extends Shape{Rectangle(){super.m_type=1;}}//圆形子类static class Circle extends Shape{Circle(){super.m_type=2;}}}
改进:
public class Test2 {public static void main(String[] args) {GraphEditor graphEditor = new GraphEditor();graphEditor.drawShape(new Rectangle());graphEditor.drawShape(new Circle());}//绘图的类(使用方)static class GraphEditor {public void drawShape(Shape shape) {shape.draw();}}/*改进:将基类抽象,增加一个抽象方法,所有子类都需实现这个抽象方法,之后增加新的业务时,就不会改动绘图的类(使用方)中的代码了*///Shape类 基类static abstract class Shape {int m_type;public abstract void draw();}//矩形子类static class Rectangle extends Shape {Rectangle() {super.m_type = 1;}@Overridepublic void draw() {System.out.println("绘制矩形");}}//圆形子类static class Circle extends Shape {Circle() {super.m_type = 2;}@Overridepublic void draw() {System.out.println("绘制圆形");}}//三角形子类//这里新增业务时只需要扩展一个子类就行,改动的代码少static class Triangle extends Shape {Triangle() {super.m_type = 2;}@Overridepublic void draw() {System.out.println("绘制三角形");}}}
1.6 迪米特法则
- 类与类之间的关系应该做到关联最少,耦合度最小
- 最少知道原则:一个类对自己依赖的类知道的越少越好
- 只与直接朋友进行通信。直接朋友:两个对象之间有耦合关系,耦合方式:依赖、关联、组合、聚合等
- 陌生的类不要以局部变量的形式出现在类的内部
问题:
public class Test1 {public static void main(String[] args) {CompanyManager e = new CompanyManager();e.printAllEmployee(new SubCompanyManager());}//总公司员工类static class Employee {private String id;public void setId(String id) {this.id = id;}public String getId() {return id;}}//分公司员工类static class SubEmployee {private String id;public void setId(String id) {this.id = id;}public String getId() {return id;}}//分公司管理类static class SubCompanyManager {//返回分公司的所有员工public List<SubEmployee> getAllEmployee() {List<SubEmployee> list = new ArrayList<SubEmployee>();for (int i = 0; i < 10; i++) {SubEmployee emp = new SubEmployee();//为分公司人员按顺序分配一个IDemp.setId("分公司" + i);list.add(emp);}return list;}}//总公司管理类//CompanyManager的直接朋友:Employee、SubCompanyManager//发现问题:SubEmployee不是直接朋友,而是一个陌生类,违反了迪米特法则static class CompanyManager {//返回总公司的员工public List<Employee> getAllEmployee() {List<Employee> list = new ArrayList<>();for (int i = 0; i < 5; i++) {Employee emp = new Employee();//为总公司人员按顺序分配一个IDemp.setId("总公司" + i);list.add(emp);}return list;}//打印所有公司的员工信息public void printAllEmployee(SubCompanyManager sub) {//分公司List<SubEmployee> list1 = sub.getAllEmployee();for (SubEmployee e : list1) {System.out.println(e.getId());}//总公司List<Employee> list2 = this.getAllEmployee();for (Employee e : list2) {System.out.println(e.getId());}}}}
改进:
public class Test2 {public static void main(String[] args) {Test1.CompanyManager e = new Test1.CompanyManager();e.printAllEmployee(new Test1.SubCompanyManager());}//总公司员工类static class Employee {private String id;public void setId(String id) {this.id = id;}public String getId() {return id;}}//分公司员工类static class SubEmployee {private String id;public void setId(String id) {this.id = id;}public String getId() {return id;}}//分公司管理类static class SubCompanyManager {//返回分公司的所有员工public List<SubEmployee> getAllEmployee() {List<SubEmployee> list = new ArrayList<>();for (int i = 0; i < 10; i++) {SubEmployee emp = new SubEmployee();//为分公司人员按顺序分配一个IDemp.setId("分公司" + i);list.add(emp);}return list;}//改进:直接将输出分公司员工的方法封装到自己的类中,降低耦合度//输出分公司人员信息public void printSubEmployee() {List<SubEmployee> list1 = getAllEmployee();for (SubEmployee e : list1) {System.out.println(e.getId());}}}//总公司管理类static class CompanyManager {//返回总公司的员工public List<Employee> getAllEmployee() {List<Employee> list = new ArrayList<>();for (int i = 0; i < 5; i++) {Employee emp = new Employee();//为总公司人员按顺序分配一个IDemp.setId("总公司" + i);list.add(emp);}return list;}//打印所有公司的员工信息public void printAllEmployee(SubCompanyManager sub) {//分公司sub.printSubEmployee();//总公司List<Employee> list2 = this.getAllEmployee();for (Employee e : list2) {System.out.println(e.getId());}}}}
1.7 合成复用
原则:尽量使用合成/聚合的方式,而不使用继承
2、UML类图
类之间的关系有继承关系,实现关系,依赖关系,关联关系,聚合关系,组合关系。
2.1 继承关系(泛化)
他是依赖关系的特例,如果A类继承了B类,我们就说A和B存在泛化关系
继承关系使用如下箭头:
由子类指向父类。
2.2 实现关系
实现关系使用如下箭头:
由实现类指向接口
2.3 依赖关系
依赖关系使用如下箭头:
由使用者指向被使用者。
如果A指向B,则说明A中使用了B,使用方式包括A类中有B类实例化对象的局部变量。A类中有方法把B类实例化对象当做了参数,A类中有方法调用了B类中的静态方法。
//PersonServiceBean类中使用到了四个类。public class PersonServiceBean {private PersonDao personDao;public void save(Person person) {}public IDCard getIDCard(Integer personid) {return null;}public void modify() {Department department = new Department();}}public class PersonDao{}public class IDCard{}public class Person{}public class Department{}
UML类图如下:
2.4 关联关系
一种结构关系,指明事物的对象之间的联系:类与类之间的联系
关联关系使用如下箭头:
由拥有者指向被拥有者。如果A指向B,则说明A类中有B类的成员变量。
用户拥有并使用计算机:
2.5 聚合关系
聚合关系使用如下箭头:
是整体与部分的关系,且部分可以离开整体而单独存在。;如果B指向A,则说明A类中有B类的成员变量,但是与关联关系不同,A类和B类有逻辑关系。A类是整体,B类是部分。A类由B类构成,同时B类即便不在A类中也可以单独存在。
大学由多个学院聚合而成:
2.6 组合关系
组合关系使用如下箭头:
是整体与部分的关系,但部分不能离开整体而单独存在。如公司和部门是整体和部分的关系,没有公司就不存在部门。
菜单、按钮共同组合成窗口,与窗口不可分离:
3、创建型模式
3.1 单例模式
3.1.1 饿汉式
步骤:
- 构造器私有化(防止new)
- 类的内部创建对象
- 向外暴露一个静态的公共方法
- 代码实现
//方式一:静态变量public class Test1 {public static void main(String[] args) {//测试Singleton instance=Singleton.getInstance();Singleton instance2=Singleton.getInstance();System.out.println(instance==instance2); //true 单例模式下2次返回的实例是相同的}}class Singleton{//1.构造器私有化,外部不能newprivate Singleton(){}//2.内部创建一个对象实例private final static Singleton instance=new Singleton();//3.提供一个共有的静态方法,返回实例对象public static Singleton getInstance(){return instance;}}//方式二:静态代码块class Singleton2{//1.构造器私有化,外部不能newprivate Singleton2(){}//2.内部创建一个对象实例private static Singleton2 instance;//静态代码块中创建单例对象static {instance = new Singleton2();}//3.提供一个共有的静态方法,返回实例对象public static Singleton2 getInstance(){return instance;}}
分析:
- 写法简单,在类加载的时候就完成了实例化,避免了线程同步的问题
- 缺点:在类加载时完成实例化,没有达到懒加载的效果;
- 如果从始至终没有使用的话,容易造成内存浪费
3.1.2 懒汉式
步骤与饿汉式相似
class Singleton3{//静态实例private static Singleton3 instance;//构造器私有化private Singleton3(){}//懒汉式:线程不安全 提供一个静态公有方法,当使用到该方法时,才去创建instancepublic static Singleton3 getInstance(){if(instance==null){instance=new Singleton3();}return instance;}}
分析:
- 起到了懒加载的效果,但只能在单线程下才能使用
- 多线程下,如果一个线程进入了if判断还未退出,另一个线程也通过这个if判断,就会产生多个实例,所以多线程下不能使用
- 实际开发中,不要使用这种方式
加入同步代码解决线程不安全问题:
class Singleton4 {//静态实例private static Singleton4 instance;//构造器私有化private Singleton4() {}//懒汉式:提供一个静态的共有方法,加入同步处理代码synchronized关键字,解决线程不安全问题public static synchronized Singleton4 getInstance() {if (instance == null) {instance = new Singleton4();}return instance;}}
分析:
- 虽然解决了线程不安全问题,但效率太低了,不推荐使用
- 每个线程想获得类的实例时,都需要执行getInstance()方法new一个实例对象,而单例模式中只需要new一次就行了,其他时候可以直接return
3.1.3 双重检查(推荐)
class Singleton5 {//静态实例private static volatile Singleton5 instance;//构造器私有化private Singleton5() {}//提供公有静态方法,加入双重检查,解决线程安全问题,同时实现懒加载public static Singleton5 getInstance() {if (instance == null) {synchronized (Singleton5.class) {if (instance == null) {instance = new Singleton5();}}}return instance;}}
分析:
- Double-Check中进行了2次if (instance == null)判断,这样就能保证线程的安全性
- 且实例化代码只进行了一次,后续的线程在第一次if判断时就退出了,直接return
- 延迟加载、效率高
3.1.4 静态内部类(推荐)
class Singleton6 {//构造器私有化private Singleton6() {}//写一个静态内部类,该类中有一个静态属性private static class SingletonInstance{private static final Singleton6 INSTANCE=new Singleton6();}//提供共有静态方法,直接返回public static Singleton6 getInstance() {return SingletonInstance.INSTANCE;}}
分析:
- 采用了类装载机制来保证初始化实例只有一个线程
- 静态内部类方式在Singleton类加载时并不会立即实例化,而是需要在实例化时调用getInstance()方法完成实例化
- JVM保证了线程安全,并实现了懒加载
3.1.5 枚举(推荐)
public class Test7 {public static void main(String[] args) {Singleton7 instance =Singleton7.INSTANCE;Singleton7 instance2 = Singleton7.INSTANCE;System.out.println(instance == instance2); //true 2次返回的实例是相同的}}//使用枚举实现单例enum Singleton7 {INSTANCE;public void sayOK() {System.out.println("OK!");}}
能避免多线程同步问题,还能防止反序列化问题
3.1.6 jdk源码分析
java中的RunTime就使用了单例模式中的饿汉式
public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class <code>Runtime</code> are instance* methods and must be invoked with respect to the current runtime object.** @return the <code>Runtime</code> object associated with the current* Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}}
3.2 工厂模式
3.2.1 简单工厂模式
定义了一个创建对象的类,由这个类来封装实例化对象的行为
需求:客户需要不同口味的披萨
代码实现:
披萨基类:
//披萨类public abstract class Pizza {protected String name;//准备制作,不同的披萨原材料不一样public abstract void prepare();//烘烤public void bake() {System.out.println(name + " baking;");}//切片public void cut() {System.out.println(name + " cutting;");}//打包public void box() {System.out.println(name + " boxing;");}public void setName(String name) {this.name = name;}}
披萨子类:略
简单工厂:
//简单工厂类:当需要增加其他披萨业务时,只需要增加相应的披萨子类,并修改简单工厂的代码即可,并不会改动orderPizza中的代码public class SimpleFactory {Pizza pizza = null;//根据orderType返回Pizza对象public Pizza creatPizza(String orderType) {if (orderType.equals("greek")) {pizza = new GreekPizza();} else if (orderType.equals("cheese")) {pizza = new CheesePizza();}return pizza;}}
模拟客户端披萨订购:
public class Test1 {//模拟客户端public static void main(String[] args) {new orderPizza(new SimpleFactory());}//订购披萨,实际业务中,订购披萨的orderPizza类会有多个,所以能不修改其代码就不修改static class orderPizza {//定义一个简单工厂对象SimpleFactory simpleFactory;//构造器public orderPizza(SimpleFactory simpleFactory) {setFactory(simpleFactory);}public void setFactory(SimpleFactory simpleFactory) {String orderType = "";this.simpleFactory = simpleFactory;orderType = getType();Pizza pizza = this.simpleFactory.creatPizza(orderType);if (pizza != null) {pizza.prepare();pizza.bake();pizza.cut();pizza.box();} else {System.out.println("订购失败!");}}//动态获取用户订购的披萨种类private String getType() {try {BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));System.out.println("input pizza type:");String str = strin.readLine();return str;} catch (IOException e) {e.printStackTrace();return "";}}}}
3.2.2 工厂方法模式
定义一个创建对象的抽象方法,由子类决定要实例化的类,把对象的实例化推迟到子类实现
项目需求:客户在点披萨时不仅有不同口味需求,位置也不一样,如北京奶酪、北京胡椒、伦敦奶酪、伦敦胡椒
实现:将披萨项目的实例化功能(creatPizza方法)抽象为抽象方法
更改orderPizza:
public abstract class OrderPizza {public OrderPizza() {Pizza pizza = null;String ordertype;do {ordertype = gettype();pizza = createPizza(ordertype);if (pizza != null) {pizza.prepare();pizza.bake();pizza.cut();pizza.box();} else {break;}} while (true);}abstract Pizza createPizza(String ordertype);}
抽象creatPizza方法:
//先根据位置不同,调用对应的OrderPizza的继承子类,再判断口味public class BJOrderPizza extends OrderPizza {@OverridePizza createPizza(String type) {Pizza pizza = null;if (type.equals("cheese")) {//在子类中进行对象的实例化pizza = new BJCheesePizza();} else if (type.equals("pepper")) {pizza = new BJPepperPizza();}return pizza;}}public class LDOrderPizza extends OrderPizza {....}
3.2.3 抽象工厂
定义:定义了一个interface用于创建相关或有依赖的对象簇,不需指明具体的类,是简单工厂和工厂方法的结合
//声明AbsFactory接口public interface AbsFactory {//让不同的工厂类来实现createPizza方法public Pizza createPizza(String type);}
//北京披萨工厂public class BJFactory implements AbsFactory {@Overridepublic Pizza createPizza(String type) {Pizza pizza = null;if (type.equals("cheese")) {pizza = new BJCheesePizza();} else if (type.equals("pepper")) {pizza = new BJPepperPizza();}return pizza;}}//伦敦披萨工厂public class LDFactory implements AbsFactory {...}
orderPizza:
public class OrderPizza {AbsFactory factory;//构造器public OrderPizza(AbsFactory factory) {setFactory(factory);}public void setFactory(AbsFactory factory) {Pizza pizza = null;String ordertype;this.factory = factory;do {ordertype = gettype();pizza = factory.createPizza(ordertype);if (pizza != null) {pizza.prepare();pizza.bake();pizza.cut();pizza.box();} else {break;}} while (true);}}
3.2.4 jdk源码分析
//java中的Calendar类使用了工厂模式Calendar calendar=Calendar.getInstance();
public static Calendar getInstance(TimeZone zone,Locale aLocale){return createCalendar(zone, aLocale);}private static Calendar createCalendar(TimeZone zone,Locale aLocale){CalendarProvider provider =LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale).getCalendarProvider();if (provider != null) {try {return provider.getInstance(zone, aLocale);} catch (IllegalArgumentException iae) {// fall back to the default instantiation}}Calendar cal = null;if (aLocale.hasExtensions()) {String caltype = aLocale.getUnicodeLocaleType("ca");if (caltype != null) {switch (caltype) {case "buddhist":cal = new BuddhistCalendar(zone, aLocale);break;case "japanese":cal = new JapaneseImperialCalendar(zone, aLocale);break;case "gregory":cal = new GregorianCalendar(zone, aLocale);break;}}}return cal;}
3.3 原型模式
- 用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象 对象.clone()
- 原型模式是一种创建型模式,允许一个对象再创建另外一个可定制的对象,无需知道创建的细节
3.3.1 问题引出
需要创建和tom羊一模一样的5只羊
sheep类:
public class Sheep {private String name;private Integer age;private String color;//set、get方法省略public Sheep(String name, Integer age, String color) {this.name = name;this.age = age;this.color = color;}@Overridepublic String toString() {return "Sheep{" +"name='" + name + '\'' +", age=" + age +", color='" + color + '\'' +'}';}}
客户端:
public class Test1 {public static void main(String[] args) {Sheep sheep1 = new Sheep("tom", 3, "白色");Sheep sheep2 = new Sheep(sheep1.getName(),sheep1.getAge(),sheep1.getColor());Sheep sheep3 = new Sheep(sheep1.getName(),sheep1.getAge(),sheep1.getColor());Sheep sheep4 = new Sheep(sheep1.getName(),sheep1.getAge(),sheep1.getColor());}}
分析:
- 简单易操作
- 在创建新对象时,总是需要获取原始对象的属性,创建效率低、不灵活
3.3.2 使用原型模式改进
//实现Cloneable接口,使用clone方法public class Sheep implements Cloneable{private String name;private Integer age;private String color;//...//克隆实例,使用默认clone方法@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}}
public class Test1 {public static void main(String[] args) throws CloneNotSupportedException {//原型模式完成克隆Sheep sheep = new Sheep("tom", 3, "白色");Sheep sheep2 = (Sheep) sheep.clone();Sheep sheep3 = (Sheep) sheep.clone();Sheep sheep4 = (Sheep) sheep.clone();Sheep sheep5 = (Sheep) sheep.clone();//克隆只是属性相同,并不是同一个对象System.out.println(sheep==sheep2); //false}}
spring的bean配置就使用了原型模式
<bean id="id01" class="com.atguigu.spring.bean.Monster" scope="prototype"/>
3.3.3 浅拷贝
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
- 上面的克隆羊就是浅拷贝
- 浅拷贝是使用默认的 clone()方法来实现
3.3.4 深拷贝
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝
- 深拷贝实现方式1:重写clone方法来实现深拷贝
- 深拷贝实现方式2:通过对象序列化实现深拷贝(推荐)
4、结构性模式
4.1 装饰模式
定义:动态的将新功能附加到对象上,在对象功能扩展方面,它比基础更有弹性,装饰着模式也体现了开闭原则
4.1.1 问题引出
解决项目:
星巴克咖啡订单项目(咖啡馆):
咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
调料:Milk、Soy(豆浆)、Chocolate
要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
使用OO的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合。
问题:Espress && Milk 就是单品咖啡+调料, 这种组合很多;会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸
4.1.2 装饰者模式解决
Drink抽象类:
public abstract class Drink {public String des; //描述private float price=0.0f; //价格//set、get方法省略//计算费用的抽象方法,由子类实现public abstract float cost();}
Coffee类:
//Coffee基类public class Coffee extends Drink{public float cost() {return super.getPrice();}}//咖啡1 继承Coffeepublic class Coffee1 extends Coffee{public Coffee1(){setDes("咖啡1");setPrice(6.0f);}}//咖啡2 继承Coffeepublic class Coffee2 extends Coffee{public Coffee2(){setDes("咖啡2");setPrice(5.0f);}}
装饰者:
//装饰者模式的装饰者 给咖啡加调料public class Decorator extends Drink {private Drink obj;//构造器public Decorator(Drink obj){this.obj=obj;}//getPrice()获得调料的价格,再加上原咖啡的价格@Overridepublic float cost() {return super.getPrice()+obj.cost();}@Overridepublic String getDes() {//调料的描述、价格、原咖啡的描述return super.des+":"+super.getPrice()+"--"+obj.getDes();}}//巧克力调料public class Chocolate extends Decorator {public Chocolate(Drink obj) {super(obj);setDes("巧克力");setPrice(1.0f);}}//牛奶调料public class Milk extends Decorator{public Milk(Drink obj) {super(obj);setDes("牛奶");setPrice(2.0f);}}
测试:
//模拟客户端下订单public class Test {public static void main(String[] args) {//装饰者模式下订单:coffee1+2份巧克力+一份牛奶//1. 点coffee1Drink order=new Coffee1();//2.加一份牛奶order=new Milk(order);System.out.println(order.getDes()+" 总价:"+order.cost());//3.加两份巧克力order=new Chocolate(order);order=new Chocolate(order);System.out.println(order.getDes()+" 总价:"+order.cost());}}//结果:/*牛奶:2.0--咖啡1 总价:8.0巧克力:1.0--巧克力:1.0--牛奶:2.0--咖啡1 总价:10.0*/
4.1.3 jdk源码分析
Java的IO结构,FilterInputStream就是一个装饰者
public abstract class InputStream implements Closeable {} //InputStream是一个抽象类,即Component 类似Drinkclass FileInputStream extends InputStream{} //FileInputStream是InputStream是一个子类,类似coffee1、coffee2public class FilterInputStream extends InputStream {} //是InputStream是一个抽象类的子类,一个装饰者类 类似Decoratorclass DataInputStream extends FilterInputStream implements DataInput {} //FilterInputStream 子类,具体的修饰者,类似Milk、Chocolateprotected volatile InputStream in; //被装饰的对象
4.2 代理模式
- 为一个对象提供一个替身,以控制这个对象的访问。通过代理对象访问目标对象
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 主要有三种静态代理、动态代理 (JDK代理、接口代理)、 Cglib代理(可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴。
在AOP编程中如何选择代理模式:
- 目标对象需要实现接口,用JDK代理
- 目标对象不需要实现接口,用Cglib代理
4.2.1 静态代理
- 在不修改目标对象的前提下,能通过代理对象对目标功能扩展
- 缺点:代理对象和目标对象需要实现一样的接口,所以会有很多代理类,一但接口方法增加,需要维护的代码很多
//业务:代理角色代理老师授课//被代理对象 真实角色的接口public interface ITeacherDao {void teach();}//被代理对象的实现类public class TeacherDao implements ITeacherDao{@Overridepublic void teach() {System.out.println("老师授课!");}}//静态代理类 代理角色public class TeacherDaoProxy implements ITeacherDao {//被代理的对象 目标对象private ITeacherDao target;public TeacherDaoProxy(ITeacherDao target) {this.target = target;}//代理对象代理真实对象执行动作@Overridepublic void teach() {System.out.println("代理开始");target.teach();System.out.println("代理结束");}}//测试//模拟客户端调度public class Test {public static void main(String[] args) {//创建目标对象TeacherDao和代理对象TeacherDaoProxyTeacherDaoProxy tp=new TeacherDaoProxy(new TeacherDao());//执行代理对象的方法,但本质还是执行的被代理对象的teach方法tp.teach();}}
4.2.2 动态代理
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK代理、接口代理
//被代理对象 真实角色的接口public interface ITeacherDao {void teach();}//被代理对象的实现类public class TeacherDao implements ITeacherDao{@Overridepublic void teach() {System.out.println("老师授课!");}}//动态代理对象public class ProxyFactory {//维护目标对象private Object target;//构造器public ProxyFactory(Object target) {this.target = target;}//给目标对象生成一个代理对象/*public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)newProxyInstance的三个参数:ClassLoader loader:指定当前目标对象的类加载器Class<?>[] interfaces:目标对象实现的接口类型,使用泛型确认类型InvocationHandler h:事件处理,执行目标对象方法时,触发*/public Object getProxyInstance() {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("JDK代理开始!");Object invoke = method.invoke(target, args);System.out.println("JDK代理结束");return invoke;}});}}//测试://模拟动态代理客户端public class Test2 {public static void main(String[] args) {//目标对象TeacherDao target=new TeacherDao();//给目标对象创建代理对象,强转类型ITeacherDao proxyFactory = (ITeacherDao) new ProxyFactory(target).getProxyInstance();proxyFactory.teach();}}
4.4.3 Cglib代理
- 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理
- Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, Cglib代理也归属到动态代理。
- Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
实现步骤:
需要引入cglib的jar文件
在内存中动态构建子类,注意代理的类不能为final,否则报错java.lang.IllegalArgumentException:
目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
//被代理对象 目标对象public class TeacherDao {public void teach() {System.out.println("cglib代理 老师授课!");}}//cglib代理工厂public class ProxyFactory implements MethodInterceptor {//维护一个目标对象private Object target;//构造器,传入一个被代理的对象public ProxyFactory(Object target) {this.target = target;}//返回一个代理对象: 是 target 对象的代理对象public Object getProxyInstance() {//1. 创建一个工具类Enhancer enhancer = new Enhancer();//2. 设置父类enhancer.setSuperclass(target.getClass());//3. 设置回调函数enhancer.setCallback(this);//4. 创建子类对象,即代理对象return enhancer.create();}//重写 intercept 方法,会调用目标对象的方法@Overridepublic Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {// TODO Auto-generated method stubSystem.out.println("Cglib代理模式 开始");Object returnVal = method.invoke(target, args);System.out.println("Cglib代理模 结束");return returnVal;}}//模拟客户端public class Test3 {public static void main(String[] args) {//创建目标对象TeacherDao target = new TeacherDao();//获取到代理对象,并且将目标对象传递给代理对象TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();//执行代理对象的方法,触发intecept 方法,从而实现 对目标对象的调用String res = proxyInstance.teach();System.out.println("res=" + res);}}
5、行为型模式
5.1 观察者模式
5.1.1 问题引出
项目需求:
- 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)
- 需要设计开放型API,便于其他第三方也能接入气象站获取数据
- 提供温度、气压和湿度的接口
- 测量数据更新时,要能实时的通知给第三方
普通实现方案:
//天气信息的主类,当有数据更新时,就调用currentConditions的update方法public class WeatherData {private float temperature;private float pressure;private float humidity;//信息接入方private CurrentConditions currentConditions;public WeatherData(CurrentConditions currentConditions) {this.currentConditions = currentConditions;}//get方法省略public void dataChange() {//接入方1currentConditions.update(getTemperature(), getPressure(), getHumidity());//接入方2//接入方3}public void setData(float temperature, float pressure, float humidity) {this.temperature = temperature;this.pressure = pressure;this.humidity = humidity;dataChange();}}//第三方公告板:接入方显示当前天气情况public class CurrentConditions {//温度、气压、湿度private float temperature;private float pressure;private float humidity;//更新天气public void update(float temperature, float pressure, float humidity) {this.temperature = temperature;this.pressure = pressure;this.humidity = humidity;display();}//展示public void display() {System.out.println("***Today mTemperature: " + temperature + "***");System.out.println("***Today mPressure: " + pressure + "***");System.out.println("***Today mHumidity: " + humidity + "***");}}//测试//模拟客户端public class Test1 {public static void main(String[] args) {CurrentConditions currentConditions = new CurrentConditions();WeatherData weatherData = new WeatherData(currentConditions);weatherData.setData(30, 150, 40);}}
分析:
- 其他第三方接入气象站获取数据的问题
- 无法在运行时动态的添加其他第三方网站
- 违反ocp原则:每次修改时都需要修改WeatherData类中的dataChange方法,且还需创建一个对应的第三方公告板
5.1.2 使用观察者模式解决
第三方接入口不在需要依赖weatherDate,当增加第三方时就不用修改weatherData中的代码,直接在Observer下增加对应实现类就行了
//接口 WeatherData实现public interface Subject {public void register(Observer observer);public void removeObserver(Observer observer);public void notifyObservers();}//天气信息处理类,含观察者集合,当数据更新时,直接通知接入方更新信息public class WeatherData implements Subject {private float temperature;private float pressure;private float humidity;//信息接入方 含多个private ArrayList<Observer> observers;public WeatherData() {observers=new ArrayList<>();}public void dataChange(){notifyObservers();}public void setData(float temperature, float pressure, float humidity) {this.temperature = temperature;this.pressure = pressure;this.humidity = humidity;dataChange();}public void register(Observer observer) {//注册一个观察者observers.add(observer);}public void removeObserver(Observer observer) {//移除一个观察者observers.remove(observer);}public void notifyObservers() {for (Observer tmp : observers) {tmp.update(this.temperature,this.pressure,this.humidity);}}}
//观察者接口public interface Observer {public void update(float temperature,float pressure,float humidity);}//CurrentCondition观察者 实现Observer接口public class CurrentCondition implements Observer{//温度、气压、湿度private float temperature;private float pressure;private float humidity;//更新天气public void update(float temperature, float pressure, float humidity) {this.temperature = temperature;this.pressure = pressure;this.humidity = humidity;display();}//展示public void display() {System.out.println("***CurrentCondition mTemperature: " + temperature + "***");System.out.println("***CurrentCondition mPressure: " + pressure + "***");System.out.println("***CurrentCondition mHumidity: " + humidity + "***");}}//其他观察者...
//测试://模拟观察者模式客户端public class Test2 {public static void main(String[] args) {//创建weatherData消息中心WeatherData weatherData = new WeatherData();//创建currentCondition观察者CurrentCondition currentCondition = new CurrentCondition();//注册currentCondition观察者weatherData.register(currentCondition);weatherData.setData(10, 200, 10);}}
5.1.3 jdk源码分析
Jdk的Observable类就使用了观察者模式
//Observable类似subject,只是一个是类一个是接口public class Observable {private boolean changed = false;private Vector<Observer> obs;/** Construct an Observable with zero Observers. */public Observable() {obs = new Vector<>();}public synchronized void addObserver(Observer o) {if (o == null)throw new NullPointerException();if (!obs.contains(o)) {obs.addElement(o);}}public synchronized void deleteObserver(Observer o) {obs.removeElement(o);}public void notifyObservers() {notifyObservers(null);}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);}protected synchronized void setChanged() {changed = true;}
