Java设计模式笔记(一)
Java设计模式笔记(一)
(23种设计模式由于篇幅较大分为两篇展示)
一、设计模式介绍
1、设计模式的目的
让程序具有更好的:
- 代码重用性
- 可读性
- 可扩展性
- 可靠性
- 高内聚,低耦合
2、设计模式的七大原则
-
单一职责原则
一个类只负责一项职责,降低类的复杂度,提高类的可读性和可维护性,降低变更引起的风险,
-
接口隔离原则
一个类对另一个类的依赖应该建立在最小接口上
上述图片,类A只需要接口中的1方法,但是却要实现2345方法,这就违背了最小接口原则,对其进行改进:
对接口拆分成几个独立的接口,采用接口隔离原则
-
依赖倒置原则
-
高层模块不应该依赖底层模块,二者都应该依赖其抽象
-
抽象不应该依赖细节,细节应该依赖抽象
-
依赖倒转(倒置)的中心思想是面向接口编程
-
使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
如图所示:
如果之后有个短信业务,则需要再person中在加个短信的业务,对其进行改进
引入接口IReceiver,Person类与接口IReceiver发生依赖关系,及时之后又短信业务也只需要实现IReceiver接口即可。
-
-
里氏替换原则
继承性说明:
父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
继承性弊端:
使用继承会给程序带来侵入性,程序的可移植性降低增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生
介绍:
简单说就是:所有引用基类的地方必须透明的使用其子类的对象。
在继承时,子类中尽量不要重写父类中的方法
上图中,父类中的add方法是a+b,但是子类B重写了父类的add方法将其改为了a-b,当程序调用的时候可能会出现错误。
采用依赖、聚合、组合的方法替换,抽取一个共同的父类,在使用C的时候还可使用B的原始方法。
1、创建一个基类
class Base { }
2、A类
class A extends Base { public int fun1(int a, int b) { return a+b; } }
3、B类
class B extends Base { private A a = new A(); // 写自己的方法 public int fun1(int a, int b) { return a+b; } // 仍然使用A的方法 public int fun2(int a, int b) { return this.a.fun1(a,b); } }
-
开闭原则
是编程中最基础、最重要的设计原则;当软件需要修改时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
代码演示:
//这是一个用于绘图的类 class GraphicEditor { //接收 Shape 对象,然后根据 type,来绘制不同的图形 public void drawShape(Shape s){ if(s.m_type=-1) drawRectangle(s); else if(s.m type-2) drawCircle(s); else if(s.m_type - 3) drawTriangle(s) } //绘制矩形 public void drawRectangle(Shape r){ System.out.printn(”绘制矩形"); } //绘制圆形 public void drawCircle(Shape r){ System.out.println("绘制圆形"); } //绘制三角形 public void drawTriangle(Shape r){ System.out.println(”绘制三角形"); } } //Shape 类,基类 class Shape { int m_type; } class Rectangle extends Shape { Rectangle(){ super.m_type=1; } } class Circle extends Shape { Circle() { super.m_type =2; } } //新增画三角形 class Triangle extends Shape { Triangle() { super.m_type = 3; } } // 开始调用 public class Ocp { public static void main(Stringll args){ //使用看看存在的问题 GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle()); } }
上述代码的优缺点:
1、比较好理解
2、缺点就是违反了设计模式的ocp原则。即对扩展开放,对修改关闭,当要新增功能时GraphicEditor需要做修改。
改进:
把创建 Shape 类做成抽象类,并提供一个抽象的 draw方法,让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw方法即可,使用方的代码就不需要修 -> 满足了开闭原则
//这是一个用于绘图的类 class GraphicEditor { //接收 Shape 对象调用draw方法 public void drawShape(Shape s){ s.draw(); } } //Shape 类,基类 abstract class Shape { int m_type; public abstract void draw();//抽象方法 } class Rectangle extends Shape { Rectangle(){ super.m_type=1; } @Override public void draw(){ // TODO Auto-generated method stub System.out.println("绘制矩形"); } } class Circle extends Shape { Circle() { super.m_type =2; } @Override public void draw(){ // TODO Auto-generated method stub System.out.println("绘制圆形"); } } //新增画三角形 class Triangle extends Shape { Triangle() { super.m_type = 3; } @Override public void draw(){ // TODO Auto-generated method stub System.out.println("绘制三角形"); } } // 开始调用 public class Ocp { public static void main(Stringll args){ //使用看看存在的问题 GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle()); } }
-
迪米特法则
迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息。
-
合成复用原则
尽量使用合成/聚合的方式,而不是使用继承;
二、设计模式概述
1、介绍
设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design patern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式的本质提高软件的维护性,通用性和扩展性,并降低软件的复杂度,
2、设计模式类型
设计模式分为三种类型,共 23种
1)创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
3)行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式.解释器模式(Interpreter 模式)、状态模式、策略模式、职责链模式(责任链模式)。
三、单例设计模式
1、介绍
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
2、单例模式的8种方式
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
(1)饿汉式(静态常量)
实现步骤:
-
构造器私有化(防止 new )
-
类的内部创建对象
-
向外暴露一个静态的公共方法。getlnstance
-
代码实现
//饿汉式(静态变量)
class Singleton {
//1.构造器私有化,外部能 new
private Singleton() {
}
//2.本类内部创建对象实例
private final static Singleton instance = new Singleton().
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
public class SingletonTest {
public static void main(String|largs){
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance .hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode()):
}
}
说明:
1)优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
2)缺点:在类装载的时候就完成实例化,没有达到LazyLoading 的效果。如果从始至终从未使用过这个实例,则 会造成内存的浪费
3)这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大多数都是调用 getnstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance 就没有达到 lazy loading 的效果
(2)饿汉式(静态代码块)
public class Singleton {
//1.构造器私有化,外部能 new
private Singleton(){}
//2.本类内部创建实例对象
private static Singleton instance;
//3. 在静态代码块中创建单例对象
static {
instance = new Singleton();
}
// 4.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance (){
return instance;
}
}
说明:
1)这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
2)这种单例模式可用,但是可能造成内存浪费
(3)懒汉式(线程不安全)
public class Singleton {
//1.构造器私有化,外部能 new
private Singleton(){}
//2.本类内部创建实例对象
private static Singleton instance;
//3. 提供一个静态公有方法,当使用到该方法时,才去创建instance
public static Singleton getInstance (){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
说明:
1)起到了 Lazy Loading的效果,但是只能在单线程下使用。
2)如果在多线程下,一个线程进入了 if(singleton ==nul)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
3)在实际开发中,不要使用这种方式
(4)懒汉式(线程安全,同步方法)
public class Singleton {
//1.构造器私有化,外部能 new
private Singleton(){}
//2.本类内部创建实例对象
private static Singleton instance;
//3. 提供一个静态公有方法,加synchronized关键词保证线安全,当使用到该方法时,才去创建instance
public static synchronized Singleton getInstance (){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
说明:
1)解决了线程安全问题
2)效率太低了,每个线程在想获得类的实例时候,执行 getinstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接retumn 就行了。方法进行同步效率太低
3)在实际开发中,不推荐使用这种方式
(5)懒汉式(线程安全,同步代码块)
public class Singleton {
//1.构造器私有化,外部能 new
private Singleton(){}
//2.本类内部创建实例对象
private static Singleton instance;
//3. 提供一个静态公有方法,当使用到该方法时,才去创建instance
public static Singleton getInstance (){
if (instance == null) {
// 添加同步代码块
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
不推荐使用
(6)双重检查
public class Singleton {
//1.构造器私有化,外部能 new
private Singleton(){}
//2.本类内部创建实例对象
private static volatile Singleton instance;
//3.提供一个静态的公有方法,加入双重检査代码,解决线程安全问题,同时解决懒加载问题,同同时保证了效率,推荐使用
public static Singleton getInstance (){
if (instance == null) {
// 添加同步代码块
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
说明:
1)Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if(singleton == nul)检查,这样就可以保证线程安全了。
2)实例化代码只用执行一次,后面再次访问时,判断if(singleton= null),直接retum 实例化对象,也避免反复进行方法同步
3)线程安全;延迟加载;效率较高
4)在实际开发中推荐使用这种单例设计模式
(7)静态内部类
public class Singleton {
//1.构造器私有化,外部能 new
private Singleton(){}
//2.静态内部类
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//3.提供一个静态的公有方法,直接返回INSTANCE,推荐使用
public static Singleton getInstance (){
return SingletonInstance.INSTANCE;
}
}
说明:
1)这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
2)静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getnstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化。
3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
4)避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
5)推荐使用
(8)枚举
// 枚举形式
public enum Singleton2 {
INSTANCE;
}
public static void main(String[] args) {
Singleton2 instance = Singleton2.INSTANCE;
Singleton2 instance2 = Singleton2.INSTANCE;
System.out.println(instance == instance2);
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
}
说明:
1)这借助JDK15 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建1)新的对象。
2)这种方式是 Effective Java 作者 Josh Bloch 提倡的方式
3)推荐使用
3、单例模式在jdk中的使用
java.lang.RunTime就是经典的单例恶汉模式
4、注意事项
1)单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
3)单例模式使用的场景:
需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
四、工厂模式
1、分类
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
2、简单工厂模式
(1)介绍
1)简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
2)简单工厂模式定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
3)在软件开发中,当我们会用到大量的创建某种类或者某批对象时,就会使用到工厂模式
(2)案例
披萨的项目:要便于披萨种类的扩展,要便于维护
- 披萨的种类很多(比如 GreekPizz、CheesePizz 等)
- 披萨的制作有 prepare,bake,cut, box
- 完成披萨店订购功能。
1、创建一个pizza类
//将Pizza 类做成抽象
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;
}
}
2、希腊披萨类
public class GreekPizza extends Pizza {
@Override
public void prepare() {
// TODO Auto-generated method stub
System.out.println("给【希腊披萨】准备原材料");
}
}
3、奶酪pizza
public class CheesePizza extends Pizza {
@Override
public void prepare() {
// TODO Auto-generated method stub
System.out.println(" 给制作奶酪披萨 准备原材料 ");
}
}
4、简单工厂类
public class SimpleFactory {
//根据orderType 返回对应的Pizza 对象
public Pizza createPizza(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式");
if (orderType.equals("greek")){
pizza = new GreekPizza();
pizza.setName(" 希腊披萨 ");
} else if (orderType.equals("cheese")){
pizza = new CheesePizza();
pizza.setName(" 奶酪披萨 ");
}
return pizza;
}
//简单工厂模式 也叫 静态工厂模式
public static Pizza createPizza2(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式2");
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName(" 希腊披萨 ");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName(" 奶酪披萨 ");
}
return pizza;
}
}
5、订购pizza
public class OrderPizza {
//定义一个简单工厂对象
SimpleFactory simpleFactory;
Pizza pizza;
String orderType;
//构造器
public OrderPizza(SimpleFactory simpleFactory, String orderType){
this.simpleFactory = simpleFactory;
this.orderType = orderType;
setFactory();
}
private void setFactory() {
// 根据用户输入的类型获取队形的pizza对象
pizza = simpleFactory.createPizza(orderType);
//输出pizza
if(pizza != null) { //订购成功
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println(" 订购披萨失败 ");
}
}
}
6、测试
public static void main(String[] args) {
//使用简单工厂模式
new OrderPizza(new SimpleFactory(), "greek");
}
输出:
使用简单工厂模式
给【希腊披萨】准备原材料
希腊披萨 baking;
希腊披萨 cutting;
希腊披萨 boxing;
如果之后需要增加pizza类型,只需要在工厂中追加即可
3、工厂方法模式
(1)介绍
工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
(2)案例
披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪 pizza、北京的胡椒 pizza 或者是伦敦的奶酪 pizza、伦敦的胡椒 pizza
1、将Pizza 类做成抽象
//将Pizza 类做成抽象
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;
}
}
2、北京奶酪pizza
public class BJCheesePizza extends Pizza {
@Override
public void prepare() {
setName("北京的奶酪pizza");
System.out.println(" 北京的奶酪pizza 准备原材料");
}
}
3、北京胡椒pizza
public class BJPepperPizza extends Pizza {
@Override
public void prepare() {
// TODO Auto-generated method stub
setName("北京的胡椒pizza");
System.out.println(" 北京的胡椒pizza 准备原材料");
}
}
4、伦敦奶酪pizza
public class LDCheesePizza extends Pizza{
@Override
public void prepare() {
// TODO Auto-generated method stub
setName("伦敦的奶酪pizza");
System.out.println(" 伦敦的奶酪pizza 准备原材料");
}
}
5、伦敦胡椒pizza
public class LDPepperPizza extends Pizza{
@Override
public void prepare() {
// TODO Auto-generated method stub
setName("伦敦的胡椒pizza");
System.out.println(" 伦敦的胡椒pizza 准备原材料");
}
}
6、订购pizza抽象类
public abstract class OrderPizza {
//定义一个抽象方法,createPizza , 让各个工厂子类自己实现
abstract Pizza createPizza(String orderType);
// 构造器
public OrderPizza(String orderType) {
Pizza pizza = createPizza(orderType); //抽象方法,由工厂子类完成
//输出pizza 制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
}
7、订购北京pizza
public class BJOrderPizza extends OrderPizza {
public BJOrderPizza(String orderType) {
super(orderType);
}
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if(orderType.equals("cheese")) {
pizza = new BJCheesePizza();
} else if (orderType.equals("pepper")) {
pizza = new BJPepperPizza();
}
// TODO Auto-generated method stub
return pizza;
}
}
8、订购伦敦pizza
public class LDOrderPizza extends OrderPizza {
public LDOrderPizza(String orderType) {
super(orderType);
}
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if(orderType.equals("cheese")) {
pizza = new LDCheesePizza();
} else if (orderType.equals("pepper")) {
pizza = new LDPepperPizza();
}
// TODO Auto-generated method stub
return pizza;
}
}
9、测试
public static void main(String[] args) {
new LDOrderPizza("cheese");
}
// ==================================================
伦敦的奶酪pizza 准备原材料
伦敦的奶酪pizza baking;
伦敦的奶酪pizza cutting;
伦敦的奶酪pizza boxing;
说明:
如果后期要加一个北京西红柿pizza,只需要先实现Pizza抽象类,可以写具体的实现过程,再在BJOrderPizza类中加一个判断条件。
4、抽象工厂模式
(1)介绍
1)抽象工厂模式:定义了一个interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
2)抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
3)从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
4)将工厂抽象成两层,AbsFactory(抽象工厂)和 具体实现的工厂子类。根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
(2)案例
12345使用工厂方法模式代码
6、抽象工厂模式抽象层
//一个抽象工厂模式的抽象层(接口)
public interface AbsFactory {
//让下面的工厂子类来 具体实现
public Pizza createPizza(String orderType);
}
7、北京工厂类
//这是工厂子类
public class BJFactory implements AbsFactory {
public Pizza createPizza(String orderType) {
System.out.println("~使用的是抽象工厂模式~");
// TODO Auto-generated method stub
Pizza pizza = null;
if(orderType.equals("cheese")) {
pizza = new BJCheesePizza();
} else if (orderType.equals("pepper")){
pizza = new BJPepperPizza();
}
return pizza;
}
}
8、伦敦工厂类
public class LDFactory implements AbsFactory {
public Pizza createPizza(String orderType) {
System.out.println("~使用的是抽象工厂模式~");
Pizza pizza = null;
if (orderType.equals("cheese")) {
pizza = new LDCheesePizza();
} else if (orderType.equals("pepper")) {
pizza = new LDPepperPizza();
}
return pizza;
}
}
9、订购pizza类
public class OrderPizza {
AbsFactory factory;
// 构造器
public OrderPizza(AbsFactory factory, String orderType) {
setFactory(factory,orderType);
}
private void setFactory(AbsFactory factory, String orderType) {
Pizza pizza = null;
this.factory = factory;
// factory 可能是北京的工厂子类,也可能是伦敦的工厂子类
pizza = factory.createPizza(orderType);
if (pizza != null) { // 订购ok
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购失败");
}
}
}
10、测试
public static void main(String[] args) {
new OrderPizza(new BJFactory(), "cheese");
}
// ===========================================================
~使用的是抽象工厂模式~
北京的奶酪pizza 准备原材料
北京的奶酪pizza baking;
北京的奶酪pizza cutting;
北京的奶酪pizza boxing;
5、总结
1)将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
2)创建对象实例时,不要直接 new 类,而是把这个 new 类的动作放在一个工厂的方法中,并返回。
3)不要让类继承具体类,而是继承抽象类或者是实现interface(接口)不要覆盖基类中已经实现的方法。
五、原型模式
1、介绍
1)原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
2)原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
3)工作原理是:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
2、案例
基础类
public class Sheep implements Cloneable {
private String name;
private int age;
private String color;
public Sheep friend; //是对象, 克隆是会如何处理
public Sheep(String name, int age, String color) {
super();
this.name = name;
this.age = age;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep [name=" + name + ", age=" + age + ", color=" + color + "]";
}
//克隆该实例,使用默认的clone方法来完成
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep)super.clone();
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
// TODO Auto-generated method stub
return sheep;
}
}
测试类
public static void main(String[] args) {
Sheep sheep = new Sheep("白羊", 5, "白色");
sheep.friend = new Sheep("jack", 2, "黑色");
Sheep sheep1 = (Sheep) sheep.clone();
Sheep sheep2 = (Sheep) sheep.clone();
Sheep sheep3 = (Sheep) sheep.clone();
System.out.println(sheep1 + " friend-hash: " + sheep1.friend.hashCode());
System.out.println(sheep2 + " friend-hash: " + sheep2.friend.hashCode());
System.out.println(sheep3 + " friend-hash: " + sheep3.friend.hashCode());
}
输出:
Sheep [name=白羊, age=5, color=白色] friend-hash: 460141958
Sheep [name=白羊, age=5, color=白色] friend-hash: 460141958
Sheep [name=白羊, age=5, color=白色] friend-hash: 460141958
发现在克隆sheep时,应用类型的friend只是做了浅拷贝
3、浅拷贝介绍
1)对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
2)对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值;在上述案例中就是浅拷贝。
3)浅拷贝是使用默认的 clone()方法来实现
sheep=(Sheep) super.clone():
4、深拷贝介绍
1)复制对象的所有基本数据类型的成员变量值
2)为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
3)深拷贝实现方式 :重写clone方法来实现深拷贝
4)深拷贝实现方式:通过对象序列化实现深拷贝(推荐)
5) 案例
基本类型
public class DeepCloneableTarget implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
private String cloneName;
private String cloneClass;
//构造器
public DeepCloneableTarget(String cloneName, String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
//因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
引用类型
public class DeepProtoType implements Serializable, Cloneable{
public String name; //String 属性
public DeepCloneableTarget deepCloneableTarget;// 引用类型
public DeepProtoType() {
super();
}
//深拷贝 - 方式 1 使用clone 方法
// 缺陷:如果引用类型属性的内部属性还是应用类型。则其内部的应用类型也要重写clone
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
//这里完成对基本数据类型(属性)和String的克隆
deep = super.clone();
//对引用类型的属性,进行单独处理
DeepProtoType deepProtoType = (DeepProtoType)deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();
// TODO Auto-generated method stub
return deepProtoType;
}
//深拷贝 - 方式2 通过对象的序列化实现 (推荐)
public Object deepClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType)ois.readObject();
return copyObj;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return null;
} finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
// TODO: handle exception
System.out.println(e2.getMessage());
}
}
}
}
测试
public static void main(String[] args) {
// TODO Auto-generated method stub
DeepProtoType p = new DeepProtoType();
p.name = "李四";
p.deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");
//方式2 完成深拷贝
DeepProtoType p2 = (DeepProtoType) p.deepClone();
System.out.println("p.name=" + p.name + " p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
System.out.println("p2.name=" + p.name + " p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
}
结果
p.name=李四 p.deepCloneableTarget=1836019240
p2.name=李四 p2.deepCloneableTarget=363771819
5、总结
1)创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
2)不用重新初始化对象,而是动态地获得对象运行时的状态
3)如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
4)在实现深克隆的时候可能需要比较复杂的代码
5)缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则。
六、建造者模式
1、介绍
1)建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
2)建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
2、四个角色
1)Product(产品角色):一个具体的产品对象。
2)Builder(抽象建造者):创建一个 Product 对象的各个部件指定的 接口/抽象类。
3)ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件。
4)Director(指挥者):构建一个使用 Builder 接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是隔离了客户与对象的生产过程,二是负责控制产品对象的生产过程。
3、案例
建房子需要打桩、砌墙、封顶等,不管普通房、别墅都需要这个过程。
1、House类
//产品->Product
public class House {
private String baise;
private String wall;
private String roofed;
public String getBaise() {
return baise;
}
public void setBaise(String baise) {
this.baise = baise;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRoofed(String roofed) {
this.roofed = roofed;
}
}
2、HouseBuilder抽象类
// 抽象的建造者
public abstract class HouseBuilder {
protected House house = new House();
//将建造的流程写好, 抽象的方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
//建造房子好, 将产品(房子) 返回
public House buildHouse() {
return house;
}
}
3、CommonHouse类
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
// TODO Auto-generated method stub
System.out.println(" 普通房子打地基5米 ");
}
@Override
public void buildWalls() {
// TODO Auto-generated method stub
System.out.println(" 普通房子砌墙10cm ");
}
@Override
public void roofed() {
// TODO Auto-generated method stub
System.out.println(" 普通房子屋顶 ");
}
}
4、HighBuilding
public class HighBuilding extends HouseBuilder {
@Override
public void buildBasic() {
// TODO Auto-generated method stub
System.out.println(" 高楼的打地基100米 ");
}
@Override
public void buildWalls() {
// TODO Auto-generated method stub
System.out.println(" 高楼的砌墙20cm ");
}
@Override
public void roofed() {
// TODO Auto-generated method stub
System.out.println(" 高楼的透明屋顶 ");
}
}
5、HouseDirector 类
public class HouseDirector {
HouseBuilder houseBuilder = null;
//构造器传入 houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//通过setter 传入 houseBuilder
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//如何处理建造房子的流程,交给指挥者
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
7、测试
public static void main(String[] args) {
//盖普通房子
CommonHouse commonHouse = new CommonHouse();
//准备创建房子的指挥者
HouseDirector houseDirector = new HouseDirector(commonHouse);
//完成盖房子,返回产品(普通房子)
House house = houseDirector.constructHouse();
System.out.println("--------------------------");
//盖高楼
HighBuilding highBuilding = new HighBuilding();
//重置建造者
houseDirector.setHouseBuilder(highBuilding);
//完成盖房子,返回产品(高楼)
houseDirector.constructHouse();
}
输出:
普通房子打地基5米
普通房子砌墙10cm
普通房子屋顶
高楼的打地基100米
高楼的砌墙20cm
高楼的透明屋顶
4、总结
1)客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
2)每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
3)可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,
也更方便使用程序来控制创建过程
4)增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
5)建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
6)如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式。
7)抽象工厂模式 VS 建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
七、适配器模式
1、介绍
1)适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
2)适配器模式属于结构型模式
3)主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
2、类适配器模式
(1)介绍
Adapter 类,通过继承 src 类,实现 dst 类接口,完成 src->dst 的适配
(2)案例
以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src (即被适配者),目的dst(即 目标)是 5V 直流电
1、适配器接口
//适配接口
public interface IVoltage5V {
public int output5V();
}
2、被适配的类
//被适配的类
public class Voltage220V {
//输出220V的电压,不变
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
3、适配器类
//适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
int srcV = output220V();//获取220V 电压
int dst = srcV / 44; //转成 5v
return dst;
}
}
4、使用者
public class Phone {
//充电
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压为5V, 可以充电~~");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5V, 不能充电~~");
}
}
}
5、测试
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
电压=220伏
电压为5V, 可以充电~~
(3)总结
1)Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点,因为这要求 dst 必须是接口,有一定局限性;
2)src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
3)由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。
3、对象适配器模式
(1)介绍
1)基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。 即:持有 src类,实现 dst 类接口,完成 src->dst 的适配
2)根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。
3)对象适配器模式是适配器模式常用的一种
(2)案例
3、修改类适配器第三步改为对象适配
//适配器类
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V; // 关联关系-聚合
//通过构造器,传入一个 Voltage220V 实例
public VoltageAdapter(Voltage220V voltage220v) {
this.voltage220V = voltage220v;
}
@Override
public int output5V() {
int dst = 0;
if(null != voltage220V) {
int src = voltage220V.output220V();//获取220V 电压
System.out.println("使用对象适配器,进行适配~~");
dst = src / 44;
System.out.println("适配完成,输出的电压为=" + dst);
}
return dst;
}
}
测试
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
电压=220伏
使用对象适配器,进行适配~~
适配完成,输出的电压为=5
电压为5V, 可以充电~~
(3)总结
1)对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst必须是接口。
2)使用成本更低,更灵活。
4、接口适配器模式
(1)介绍
1)核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
2)适用于一个接口不想使用其所有的方法的情况。
(2)案例
1、接口
public interface Interface4 {
public void m1();
public void m2();
public void m3();
public void m4();
}
2、抽象类
//在AbsAdapter 我们将 Interface4 的方法进行默认实现
public abstract class AbsAdapter implements Interface4 {
//默认实现
public void m1() {
}
public void m2() {
}
public void m3() {
}
public void m4() {
}
}
3、测试
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter() {
//只需要去覆盖我们 需要使用 接口方法
@Override
public void m1() {
System.out.println("使用了m1的方法");
}
};
absAdapter.m1();
}
使用了m1的方法
(3)总结
1)三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在 Adapter 里的形式)来命名的。
2)类适配器:以类给到,在 Adapter 里,就是将 src当做类,继承
对象适配器:以对象给到,在 Adapter里,将src 作为一个对象,持有
接口适配器:以接口给到,在Adapter 里,将src 作为一个接口,实现
3)Adapter 模式最大的作用还是将原本不兼容的接口融合在一起工作。
八、桥接模式
1、介绍
1)桥接模式(Bridge 模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。是一种结构型设计模式。
2)Bridge 模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstaction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。
2、原理说明
说明:
1)Client类:桥接模式的调用者
2)抽象类(Abstraction):维护了 Implementor/即它的实现类 ConcretelmplementorA…
二者是聚合关系,Abstraction充当桥接类
3)RefinedAbstraction:是 Abstraction 抽象类的子类
4)Implementor:行为实现类的接口
5)ConcretelmplementorA/B :行为的具体实现类
6)从 UM 图:这里的抽象类和接口是聚合的关系,其实调用和被调用关系
3、案例
手机打电话
传统类图:
扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。违反了单一职责原则,这样增加了代码维护成本
改进类图:
1、接口Brand
//接口
public interface Brand {
void open();
void close();
void call();
}
2、vivo手机
public class Vivo implements Brand {
@Override
public void open() {
System.out.println(" Vivo手机开机 ");
}
@Override
public void close() {
System.out.println(" Vivo手机关机 ");
}
@Override
public void call() {
System.out.println(" Vivo手机打电话 ");
}
}
3、小米手机
public class XiaoMi implements Brand {
@Override
public void open() {
System.out.println(" 小米手机开机 ");
}
@Override
public void close() {
System.out.println(" 小米手机关机 ");
}
@Override
public void call() {
System.out.println(" 小米手机打电话 ");
}
}
4、手机抽象类
public abstract class Phone {
//组合品牌
private Brand brand;
//构造器
public Phone(Brand brand) {
super();
this.brand = brand;
}
protected void open() {
brand.open();
}
protected void close() {
brand.close();
}
protected void call() {
brand.call();
}
}
5、折叠手机
//折叠式手机类,继承 抽象类 Phone
public class FoldedPhone extends Phone {
//构造器
public FoldedPhone(Brand brand) {
super(brand);
}
public void open() {
super.open();
System.out.println(" 折叠样式手机 ");
}
public void close() {
super.close();
System.out.println(" 折叠样式手机 ");
}
public void call() {
super.call();
System.out.println(" 折叠样式手机 ");
}
}
6、直立手机
public class UpRightPhone extends Phone {
//构造器
public UpRightPhone(Brand brand) {
super(brand);
}
public void open() {
super.open();
System.out.println(" 直立样式手机 ");
}
public void close() {
super.close();
System.out.println(" 直立样式手机 ");
}
public void call() {
super.call();
System.out.println(" 直立样式手机 ");
}
}
7、测试
public static void main(String[] args) {
//获取折叠式手机 (样式 + 品牌 )
Phone phone1 = new FoldedPhone(new XiaoMi());
phone1.open();
phone1.call();
phone1.close();
System.out.println("=======================");
Phone phone2 = new FoldedPhone(new Vivo());
phone2.open();
phone2.call();
phone2.close();
}
小米手机开机
折叠样式手机
小米手机打电话
折叠样式手机
小米手机关机
折叠样式手机
Vivo手机开机
折叠样式手机
Vivo手机打电话
折叠样式手机
Vivo手机关机
折叠样式手机
4、总结
1)实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
2)对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
3)桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
4)桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设
计和编程
5)桥接模式要求正确识别出系统中两个独立变化的维度(抽象、和实现),因此其使用范围有一定的局限性,。
6)应用场景
- JDBC 驱动程序
- 银行转账系统
- 转账分类:网上转账,柜台转账,AMT转账
- 转账用户类型:普通用户,银卡用户,金卡用户
- 消息管理
- 消息类型:即时消息,延时消息
- 消息分类:手机短信,邮件消息,QQ消息
九、装饰者模式
1、介绍
装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
2、案例
1)咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
2)调料:Milk、Soy(豆浆)、Chocolate
3)要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
4)使用 OO 来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合。
传统类图:
1)可以控制类的数量,不至于造成很多的类
2)在增加或者删除调料种类时,代码的维护量很大
3)考虑到用户可以添加多份 调料时,可以将 hasMilk 返回一个对应 int
改进类图
1、drink抽象类
public abstract class Drink {
public String des; // 描述
private float price = 0.0f;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
//计算费用的抽象方法
//子类来实现
public abstract float cost();
}
2、coffee类
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
3、浓缩咖啡
public class ShortBlack extends Coffee{
public ShortBlack() {
setDes(" shortblack ");
setPrice(4.0f);
}
}
4、无因咖啡
public class DeCaf extends Coffee {
public DeCaf() {
setDes(" 无因咖啡 ");
setPrice(1.0f);
}
}
5、意大利咖啡
public class Espresso extends Coffee {
public Espresso() {
setDes(" 意大利咖啡 ");
setPrice(6.0f);
}
}
6、美式咖啡
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" longblack ");
setPrice(5.0f);
}
}
以上都是咖啡类型
7、装饰类
public class Decorator extends Drink {
private Drink obj;
public Decorator(Drink obj) { //组合
this.obj = obj;
}
@Override
public float cost() {
// getPrice 自己价格
return super.getPrice() + obj.cost();
}
@Override
public String getDes() {
// obj.getDes() 输出被装饰者的信息
return des + " " + getPrice() + " && " + obj.getDes();
}
}
8、巧克力
//具体的Decorator, 这里就是调味品
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes(" 巧克力 ");
setPrice(3.0f); // 调味品 的价格
}
}
9、牛奶
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes(" 牛奶 ");
setPrice(2.0f);
}
}
10、豆浆
public class Soy extends Decorator{
public Soy(Drink obj) {
super(obj);
setDes(" 豆浆 ");
setPrice(1.5f);
}
}
11、测试
public static void main(String[] args) {
// 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
// 1. 点一份 LongBlack
Drink order = new LongBlack();
System.out.println("费用1=" + order.cost());
System.out.println("描述=" + order.getDes());
// 2. order 加入一份牛奶
order = new Milk(order);
System.out.println("order 加入一份牛奶 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 描述 = " + order.getDes());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入一份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入2份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDes());
System.out.println("===========================");
Drink order2 = new DeCaf();
System.out.println("order2 无因咖啡 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 描述 = " + order2.getDes());
order2 = new Milk(order2);
x System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes());
}
描述= longblack
order 加入一份牛奶 费用 =7.0
order 加入一份牛奶 描述 = 牛奶 2.0 && longblack
order 加入一份牛奶 加入一份巧克力 费用 =10.0
order 加入一份牛奶 加入一份巧克力 描述 = 巧克力 3.0 && 牛奶 2.0 && longblack
order 加入一份牛奶 加入2份巧克力 费用 =13.0
order 加入一份牛奶 加入2份巧克力 描述 = 巧克力 3.0 && 巧克力 3.0 && 牛奶 2.0 && longblack
=============================================
order2 无因咖啡 费用 =1.0
order2 无因咖啡 描述 = 无因咖啡
order2 无因咖啡 加入一份牛奶 费用 =3.0
order2 无因咖啡 加入一份牛奶 描述 = 牛奶 2.0 && 无因咖啡
3、jdk中的使用
Java 的 IO 结构,FilterInputStream 就是一个装饰者
public static void main(Stringl args) throws Exception{
//说明
//1.nputStream 是抽象类,类似我们前面讲的 Drink
//2. FilelnputStream 是InputStream 子类,类似我们前面的 DeCaf, LongBlack
//3.FilterlnputStream 是 InputStream 子类:类似我们前面 的 Decorator 修饰者
//4. DatalnputStream 是 FilterlnputStream 子类,具体的修饰者,类似前面的 Mik, Soy 等
//5.FilterInputStream 类有protected volatile InputStream in;即含被装饰者
//6.分析得出在 jdk 的io体系中,就是使用装饰者模式
DatalnputStream dis = new DatalnputStream(new FileInputStream("d:\\abc.txt" ));
System.out.println(dis.read());
dis.close();
}
十、组合模式
1、介绍
1)组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示**“整体-部分”**的层次关系。
2)组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
3)组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以
及组合对象
2、原理
1)Component:这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子部件,Component 可以是抽象类或者接口
2)Leaf:在组合中表示叶子节点,叶子节点没有子节点
3)Composite:非叶子节点, 用于存储子部件, 在 Component 接口中实现 子部件的相关操作,比如增加(add),删除。
3、案例
编写程序展示一个学校院系结构:要在一个页面中展示出学校的院系组成,一个学校有多个学院,个学院有多个系。
1、抽象类
public abstract class OrganizationComponent {
private String name; // 名字
private String des; // 说明
protected void add(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
protected void remove(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
//构造器
public OrganizationComponent(String name, String des) {
super();
this.name = name;
this.des = des;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
//方法print, 做成抽象的, 子类都需要实现
protected abstract void print();
}
2、学校
//University 就是 Composite , 可以管理College
public class University extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
// 构造器
public University(String name, String des) {
super(name, des);
}
// 重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
// 重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
// print方法,就是输出University 包含的学院
@Override
protected void print() {
System.out.println("--------------" + getName() + "--------------");
//遍历 organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
3、学院
public class College extends OrganizationComponent {
//List 中 存放的Department
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
// 构造器
public College(String name, String des) {
super(name, des);
}
// 重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
// 将来实际业务中,Colleage 的 add 和 University add 不一定完全一样
organizationComponents.add(organizationComponent);
}
// 重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
// print方法,就是输出University 包含的学院
@Override
protected void print() {
System.out.println("--------------" + getName() + "--------------");
//遍历 organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
4、院系
public class Department extends OrganizationComponent {
//没有集合
public Department(String name, String des) {
super(name, des);
}
//add , remove 就不用写了,因为他是叶子节点
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
@Override
protected void print() {
System.out.println(getName());
}
}
5、测试
public static void main(String[] args) {
//从大到小创建对象 学校
OrganizationComponent university = new University("清华大学", " 中国顶级大学 ");
//创建 学院
OrganizationComponent computerCollege = new College("计算机学院", " 计算机学院 ");
OrganizationComponent infoEngineercollege = new College("信息工程学院", " 信息工程学院 ");
//创建各个学院下面的系(专业)
computerCollege.add(new Department("软件工程", " 软件工程不错 "));
computerCollege.add(new Department("网络工程", " 网络工程不错 "));
computerCollege.add(new Department("计算机科学与技术", " 计算机科学与技术是老牌的专业 "));
infoEngineercollege.add(new Department("通信工程", " 通信工程不好学 "));
infoEngineercollege.add(new Department("信息工程", " 信息工程好学 "));
//将学院加入到 学校
university.add(computerCollege);
university.add(infoEngineercollege);
university.print();
// infoEngineercollege.print();
}
--------------清华大学--------------
--------------计算机学院--------------
软件工程
网络工程
计算机科学与技术
--------------信息工程学院--------------
通信工程
信息工程
4、总结
1)简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
2)具有较强的扩展性。当要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
3)方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
4)需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式
5)要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
十一、外观模式
1、介绍
1)外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
2)外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
2、案例
组建一个家庭影院:
DVD 播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:直接用遥控器:统筹各设备开关
开爆米花机
放下屏幕
开投影仪
开音响
开 DVD,选 dvd
去拿爆米花
调暗灯光
播放
观影结束后,关闭各种设备
1、爆米花类
public class Popcorn {
private static Popcorn instance = new Popcorn();
public static Popcorn getInstance() {
return instance;
}
public void on() {
System.out.println(" popcorn on ");
}
public void off() {
System.out.println(" popcorn ff ");
}
public void pop() {
System.out.println(" popcorn is poping ");
}
}
2、屏幕类
public class Screen {
private static Screen instance = new Screen();
public static Screen getInstance() {
return instance;
}
public void up() {
System.out.println(" Screen up ");
}
public void down() {
System.out.println(" Screen down ");
}
}
3、投影仪类
public class Projector {
private static Projector instance = new Projector();
public static Projector getInstance() {
return instance;
}
public void on() {
System.out.println(" Projector on ");
}
public void off() {
System.out.println(" Projector ff ");
}
public void focus() {
System.out.println(" Projector is Projector ");
}
}
4、音响类
public class Stereo {
private static Stereo instance = new Stereo();
public static Stereo getInstance() {
return instance;
}
public void on() {
System.out.println(" Stereo on ");
}
public void off() {
System.out.println(" Screen off ");
}
public void up() {
System.out.println(" Screen up.. ");
}
}
5、dvd类
public class DVDPlayer {
//使用单例模式, 使用饿汉式
private static DVDPlayer instance = new DVDPlayer();
public static DVDPlayer getInstanc() {
return instance;
}
public void on() {
System.out.println(" dvd on ");
}
public void off() {
System.out.println(" dvd off ");
}
public void play() {
System.out.println(" dvd is playing ");
}
public void pause() {
System.out.println(" dvd pause ..");
}
}
6、灯光类
public class TheaterLight {
private static TheaterLight instance = new TheaterLight();
public static TheaterLight getInstance() {
return instance;
}
public void on() {
System.out.println(" TheaterLight on ");
}
public void off() {
System.out.println(" TheaterLight off ");
}
public void dim() {
System.out.println(" TheaterLight dim.. ");
}
public void bright() {
System.out.println(" TheaterLight bright.. ");
}
}
7、统筹类(家庭影院)
public class HomeTheaterFacade {
//定义各个子系统对象
private TheaterLight theaterLight;
private Popcorn popcorn;
private Stereo stereo;
private Projector projector;
private Screen screen;
private DVDPlayer dVDPlayer;
//构造器
public HomeTheaterFacade() {
super();
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.stereo = Stereo.getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
this.dVDPlayer = DVDPlayer.getInstanc();
}
//操作分成 4 步
public void ready() {
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
stereo.on();
dVDPlayer.on();
theaterLight.dim();
}
public void play() {
dVDPlayer.play();
}
public void pause() {
dVDPlayer.pause();
}
public void end() {
popcorn.off();
theaterLight.bright();
screen.up();
projector.off();
stereo.off();
dVDPlayer.off();
}
}
8、测试
public static void main(String[] args) {
//这里直接调用。。 很麻烦
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
// 开始
System.out.println("==============开始播放=============");
homeTheaterFacade.ready();
// 暂停
System.out.println("==============暂停=============");
homeTheaterFacade.play();
// 结束
System.out.println("==============结束=============");
homeTheaterFacade.end();
==开始播放=
popcorn on
popcorn is poping
Screen down
Projector on
Stereo on
dvd on
TheaterLight dim…
==暂停=
dvd is playing
==结束=
popcorn ff
TheaterLight bright…
Screen up
Projector ff
Screen off
dvd off
3、总结
1)外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
2)外观模式对客户端与子系统的耦合关系-解耦,让子系统内部的模块更易维护和扩展
3)通过合理的使用外观模式,可以帮我们更好的划分访问的层次
4)当系统需要进行分层设计时,可以考虑使用Facade 模式
5)在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade 类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性
十二、享元模式
1、介绍
1)享元模式(Flyweight Pattern) 也叫 蝇量模式:运用共享技术有效地支持大量细粒度的对象
2)常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
3)享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
4)享元模式经典的应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享
元模式是池技术的重要实现方式
2、内部状态与外部状态
1)享元模式提出了两个要求:细粒度和共享对象。即将对象的信息分为两个部分:内部状态和外部状态
2)内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
3)外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
3、案例
小型的外包项目,给客户 A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:
1)有客户要求以新闻的形式发布
2)有客户人要求以博客的形式发布
3)有客户希望以微信公众号的形式发布
1、外部状态user
public class User {
private String name;
public User(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2、抽象类
public abstract class WebSite {
public abstract void use(User user);//抽象方法
}
3、具体网站
//具体网站
public class ConcreteWebSite extends WebSite {
//共享的部分,内部状态
private String type = ""; //网站发布的形式(类型)
//构造器
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的发布形式为:" + type + " 在使用中 .. 使用者是" + user.getName());
}
}
4、工厂类
// 网站工厂类,根据需要返回压一个网站
public class WebSiteFactory {
//集合, 充当池的作用
private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
//根据网站的类型,返回一个网站, 如果没有就创建一个网站,并放入到池中,并返回
public WebSite getWebSiteCategory(String type) {
if(!pool.containsKey(type)) {
//就创建一个网站,并放入到池中
pool.put(type, new ConcreteWebSite(type));
}
return (WebSite)pool.get(type);
}
//获取网站分类的总数 (池中有多少个网站类型)
public int getWebSiteCount() {
return pool.size();
}
}
5、测试
public static void main(String[] args) {
// 创建一个工厂类
WebSiteFactory factory = new WebSiteFactory();
// 客户要一个以新闻形式发布的网站
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User("tom"));
// 客户要一个以博客形式发布的网站
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User("jack"));
// 客户要一个以博客形式发布的网站
WebSite webSite3 = factory.getWebSiteCategory("博客");
webSite3.use(new User("smith"));
// 客户要一个以博客形式发布的网站
WebSite webSite4 = factory.getWebSiteCategory("博客");
webSite4.use(new User("king"));
System.out.println("网站的分类共=" + factory.getWebSiteCount());
}
网站的发布形式为:新闻 在使用中 … 使用者是tom
网站的发布形式为:博客 在使用中 … 使用者是jack
网站的发布形式为:博客 在使用中 … 使用者是smith
网站的发布形式为:博客 在使用中 … 使用者是king
网站的分类共=2
4、总结
1)享元模式理解:“享”就表示共享,“元”表示对象
2)系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
3)用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable
4)存储享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
5)享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方
6)使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
7)享元模式经典的应用场景是需要缓冲池的场景,比如String 常量池、数据库连接池
十三、代理模式
1、介绍
1)代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象,优点是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
2)被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
3)代理模式有不同的形式。主要有三种 静态代理、动态代理(JDK 代理、接口代理)和 Cglib 代理(可以在内存动态的创建对象,而不需要实现接口,属于动态代理的范畴)。
2、静态代理
(1)介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。
(2)案例
1)定义一个接口:ITeacherDao
2)目标对象 TeacherDA0 实现接口 ITeacherDAO
3)使用静态代理方式,就需要在代理对象 TeacherDAOProxy 中也实现 ITeacherDAO
4)调用的时候通过调用代理对象的方法来调用目标对象
5)代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
1、接口
//接口
public interface ITeacherDao {
void teach(); // 授课的方法
}
2、目标对象
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println(" 老师授课中 。。。。。");
}
}
3、代理对象
//代理对象,静态代理
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("提交。。。。。");//方法
}
}
4、测试
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建目标对象(被代理对象)
TeacherDao teacherDao = new TeacherDao();
//创建代理对象, 同时将被代理对象传递给代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
//通过代理对象,调用到被代理对象的方法
//即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
teacherDaoProxy.teach();
}
开始代理 完成某些操作。。。。。
老师授课中 。。。。。
提交。。。。。
(3)总结
1)优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
2)缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
3)一旦接口增加方法,目标对象与代理对象都要维护
3、动态代理
(1)介绍
1)代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
2)代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
3)动态代理也叫做:JDK 代理、接口代理
4)在jdk中生成代理对象api
-
代理类所在包:java.lang.reflect.Proxy
-
JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,写法是:
static Obiect newProxyInstance(ClassLoader loader. Class<?> interfaces.InvocationHandler h)
(2)案例
将静态代理改为动态代理
1、接口
//接口
public interface ITeacherDao {
void teach(); // 授课方法
void sayHello(String name);
}
2、目标对象实现
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println(" 老师授课中.... ");
}
@Override
public void sayHello(String name) {
System.out.println("hello " + name);
}
}
3、代理
public class ProxyFactory {
//维护一个目标对象 , Object
private Object target;
//构造器 , 对target 进行初始化
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象 生成一个代理对象
public Object getProxyInstance() {
//说明
/*
* public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
//1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
//2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
//3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
*/
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 returnVal = method.invoke(target, args);
System.out.println("JDK代理提交");
return returnVal;
}
});
}
}
4、测试
public static void main(String[] args) {
//创建目标对象
ITeacherDao target = new TeacherDao();
//给目标对象,创建代理对象, 可以转成 ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
// proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
System.out.println("proxyInstance=" + proxyInstance.getClass());
//通过代理对象,调用目标对象的方法
//proxyInstance.teach();
proxyInstance.sayHello(" tom ");
}
proxyInstance=class com.sun.proxy.$Proxy0
JDK代理开始~~
hello tom
JDK代理提交
4、Cglib代理
(1)介绍
1)静态代理和JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理
2)Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理。
3)Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现 java接口,它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截
4)在 AOP 编程中如何选择代理模式:
-
目标对象需要实现接口,用 JDK 代理
-
目标对象不需要实现接口,用 Cglib 代理
5)Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
(2)实现步骤
1)引入cglib的jar包
2)在内存中动态构建子类,注意代理的类不能为fnal,否则报错 java.lang.IllegalArgumentException:
3)目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
(3)案例
1、目标对象
public class TeacherDao {
public String teach() {
System.out.println(" 老师授课中 , 我是cglib代理,不需要实现接口 ");
return "hello";
}
}
2、代理类
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 {
System.out.println("Cglib代理模式 ~~ 开始");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib代理模式 ~~ 提交");
return returnVal;
}
}
3、测试
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);
}
Cglib代理模式 ~~ 开始
老师授课中 , 我是cglib代理,不需要实现接口
Cglib代理模式 ~~ 提交
res=hello