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{
@Override
public 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;
}
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
//圆形子类
static class Circle extends Shape {
Circle() {
super.m_type = 2;
}
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
//三角形子类
//这里新增业务时只需要扩展一个子类就行,改动的代码少
static class Triangle extends Shape {
Triangle() {
super.m_type = 2;
}
@Override
public 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();
//为分公司人员按顺序分配一个ID
emp.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();
//为总公司人员按顺序分配一个ID
emp.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();
//为分公司人员按顺序分配一个ID
emp.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();
//为总公司人员按顺序分配一个ID
emp.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.构造器私有化,外部不能new
private Singleton(){
}
//2.内部创建一个对象实例
private final static Singleton instance=new Singleton();
//3.提供一个共有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
//方式二:静态代码块
class Singleton2{
//1.构造器私有化,外部不能new
private 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(){}
//懒汉式:线程不安全 提供一个静态公有方法,当使用到该方法时,才去创建instance
public 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 {
@Override
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 LDOrderPizza extends OrderPizza {....}
3.2.3 抽象工厂
定义:定义了一个interface用于创建相关或有依赖的对象簇,不需指明具体的类,是简单工厂和工厂方法的结合
//声明AbsFactory接口
public interface AbsFactory {
//让不同的工厂类来实现createPizza方法
public Pizza createPizza(String type);
}
//北京披萨工厂
public class BJFactory implements AbsFactory {
@Override
public 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;
}
@Override
public 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方法
@Override
protected 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 继承Coffee
public class Coffee1 extends Coffee{
public Coffee1(){
setDes("咖啡1");
setPrice(6.0f);
}
}
//咖啡2 继承Coffee
public 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()获得调料的价格,再加上原咖啡的价格
@Override
public float cost() {
return super.getPrice()+obj.cost();
}
@Override
public 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. 点coffee1
Drink 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 类似Drink
class FileInputStream extends InputStream{} //FileInputStream是InputStream是一个子类,类似coffee1、coffee2
public class FilterInputStream extends InputStream {} //是InputStream是一个抽象类的子类,一个装饰者类 类似Decorator
class DataInputStream extends FilterInputStream implements DataInput {} //FilterInputStream 子类,具体的修饰者,类似Milk、Chocolate
protected volatile InputStream in; //被装饰的对象
4.2 代理模式
- 为一个对象提供一个替身,以控制这个对象的访问。通过代理对象访问目标对象
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 主要有三种静态代理、动态代理 (JDK代理、接口代理)、 Cglib代理(可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴。
在AOP编程中如何选择代理模式:
- 目标对象需要实现接口,用JDK代理
- 目标对象不需要实现接口,用Cglib代理
4.2.1 静态代理
- 在不修改目标对象的前提下,能通过代理对象对目标功能扩展
- 缺点:代理对象和目标对象需要实现一样的接口,所以会有很多代理类,一但接口方法增加,需要维护的代码很多
//业务:代理角色代理老师授课
//被代理对象 真实角色的接口
public interface ITeacherDao {
void teach();
}
//被代理对象的实现类
public class TeacherDao implements ITeacherDao{
@Override
public void teach() {
System.out.println("老师授课!");
}
}
//静态代理类 代理角色
public class TeacherDaoProxy implements ITeacherDao {
//被代理的对象 目标对象
private ITeacherDao target;
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
//代理对象代理真实对象执行动作
@Override
public void teach() {
System.out.println("代理开始");
target.teach();
System.out.println("代理结束");
}
}
//测试
//模拟客户端调度
public class Test {
public static void main(String[] args) {
//创建目标对象TeacherDao和代理对象TeacherDaoProxy
TeacherDaoProxy tp=new TeacherDaoProxy(new TeacherDao());
//执行代理对象的方法,但本质还是执行的被代理对象的teach方法
tp.teach();
}
}
4.2.2 动态代理
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK代理、接口代理
//被代理对象 真实角色的接口
public interface ITeacherDao {
void teach();
}
//被代理对象的实现类
public class TeacherDao implements ITeacherDao{
@Override
public 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() {
@Override
public 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 方法,会调用目标对象的方法
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
System.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() {
//接入方1
currentConditions.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;
}