1、设计模式七大原则
解析(指导思想):
- 开闭原则(OCP):设计原则的最基本要求,后面的六大原则,都是为了保证程序符合开闭原则
- 单一职责原则:(针对类如何定义)
- 接口隔离原则:(针对接口如何定义)
- 里氏替换原则:(如何使用继承,一般是父类实现的方法,子类不要轻易覆盖)
- 依赖倒置原则:(方法的参数,返回值要使用抽象或者接口,不要使用实现类)
- 迪米特法则:(最少入职原则)A–> B --> C 不要出现A直接调用C的情况(不要和陌生人说话)
- 合成复用原则:(能使用组合关系,不要使用继承关系)A{B} A extend B
1、设计模式目的
编写软件过程中,程序员面临着来自 耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战,设计模式是为了让程序(软件),具有更好:
- 代码重用性(即:相同功能的代码,不用多次编写)
- 可读性(即:编程规范性,便于其他程序员的阅读和理解)
- 可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护)
- 可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
- 使程序呈现高内聚,低耦合的特性
- 分享金句:
a. 设计模式包含了面向对象的精髓,“懂了设计模式,你就懂了面向对象分析和设计(OOA/D)的精要”
b. Scott Mayers在其巨著《Effective C++》就曾经说过:C++老手和C++新手的区别就是前者手背上有很多伤疤
2、单一接口原则
单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多于一个导致类变更的原因。
核心:就是解耦和增强内聚性。
对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。 当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2
优点:
- 提高类的可维护性和可读写性
- 提高系统的可维护性
- 降低变更的风险
降低类的复杂度,一个类只负责一项职责。
提高类的可读性,可维护性
降低变更引起的风险
通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则。
假设我们有一个 Class 负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。这样一来,这个 Class 存在两个导 致类变更的原因。如何解决这个问题呢?我们就要给两个职责分别用两个 Class 来实现, 进行解耦。后期需求变更维护互不影响。这样的设计,可以降低类的复杂度,提高类的 可读性,提高系统的可维护性,降低变更引起的风险。总体来说就是一个 Class/Interface/Method 只负责一项职责。
下面我们来举一个实例
public interface UserOperate {
void updateUserName(UserInfo userInfo);
void updateUserPassword(UserInfo userInfo);
}
public class UserOperateImpl implements UserOperate {
@Override
public void updateUserName(UserInfo userInfo) {
// 修改用户名逻辑
}
@Override
public void updateUserPassword(UserInfo userInfo) {
// 修改密码逻辑
}
}
修改用户名和修改密码逻辑分开,各自执行各自的职责,互不干扰,功能清晰明了。
具体比较实现
方式一:
/**
* 做家务
*/
public interface HouseWork {
// 扫地
void sweepFloor();
// 购物
void shopping();
}
public class Xiaocheng implements HouseWork{
@Override
public void sweepFloor() {
// 扫地
}
@Override
public void shopping() {
}
}
public class Xiaoming implements HouseWork{
@Override
public void sweepFloor() {
}
@Override
public void shopping() {
// 买菜
}
}
说是妈妈在出门前嘱咐小明和小陈做家务(定义一个做家务接口),小明去买菜(实现买菜接口),小陈去扫地(实现扫地接口)。到这里其实你就发现,小明不需要扫地却要重写扫地的方法,小陈不需要买菜但因为实现了做家务的接口,就不得不也重写买菜方法。
这样的设计是不合理的,他违背了单一原则,也不符合开闭原则。
方法二:
public interface Cooking extends Hoursework{
void cooking();
}
public class Xiaoming implements Shopping, Cooking{
@Override
public void shopping() {
// 小明购物
}
@Override
public void cooking() {
// 小明做饭
}
}
现在我们将扫地和做家务这个接口拆分,小陈扫地,那就实现扫地接口,小明购物就实现买菜接口。
接下来小明买完菜回来做饭,那么就只需要新增一个做法接口,让小明实现就可以了
一个接口实现一个功能就好。
接口层面,子类不实现多余的接口,这就符合单一原则:一个类只做一件事,并且修改它不会带来其他变化。
3、接口隔离原则
接口隔离原则(Interface Segregation Principle),又称ISP原则
1、 客户端不应该依赖它不需要的接口
2、 类间的依赖关系应该建立在最小的接口上
介绍:
通俗的来讲,不要在一个接口中定义多个方法,接口应该尽量细化
案例实现接口隔离
public class SegregationDemo {
public static void main(String[] args) {
new FruitShop().cutApple(new CookZhang());
new FruitShop().cutTomato(new CookZhang());
new VegetableShop().cutTomato(new CookLi());
new VegetableShop().cutPotato(new CookLi());
}
}
interface Knife {
void cutApple();
void cutTomato();
void cutPotato();
}
class CookZhang implements Knife {
@Override
public void cutApple() {
System.out.println("张师傅在切苹果");
}
@Override
public void cutTomato() {
System.out.println("张师傅在切土豆");
}
@Override
public void cutPotato() {
System.out.println("张师傅在切番茄");
}
}
class CookLi implements Knife {
@Override
public void cutApple() {
System.out.println("李师傅在切苹果");
}
@Override
public void cutTomato() {
System.out.println("李师傅在切土豆");
}
@Override
public void cutPotato() {
System.out.println("李师傅在切番茄");
}
}
class FruitShop {
public void cutApple(Knife knife) {
knife.cutApple();
}
public void cutTomato(Knife knife) {
knife.cutTomato();
}
}
class VegetableShop {
public void cutPotato(Knife knife) {
knife.cutPotato();
}
public void cutTomato(Knife knife) {
knife.cutTomato();
}
}
这里会实现过多的多余的实现方法
修改改接口后的代码:
public class SegregationDemo {
public static void main(String[] args) {
new FruitShop().cutApple(new CookZhang());
new FruitShop().cutTomato(new CookZhang());
new VegetableShop().cutTomato(new CookLi());
new VegetableShop().cutPotato(new CookLi());
}
}
interface AppleKnife {
void cutApple();
}
interface TomatoKnife {
void cutTomato();
}
interface PotatoKnife {
void cutPotato();
}
class CookZhang implements AppleKnife, TomatoKnife {
@Override
public void cutApple() {
System.out.println("张师傅在切苹果");
}
@Override
public void cutTomato() {
System.out.println("张师傅在切番茄");
}
}
class CookLi implements TomatoKnife, PotatoKnife {
@Override
public void cutTomato() {
System.out.println("李师傅在切土豆");
}
@Override
public void cutPotato() {
System.out.println("李师傅在切番茄");
}
}
class FruitShop {
public void cutApple(AppleKnife knife) {
knife.cutApple();
}
public void cutTomato(TomatoKnife knife) {
knife.cutTomato();
}
}
class VegetableShop {
public void cutPotato(PotatoKnife knife) {
knife.cutPotato();
}
public void cutTomato(TomatoKnife knife) {
knife.cutTomato();
}
}
4、依赖倒置原则
依赖倒置原则的原始定义为:
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
核心就是面向接口编程;
让细节的具体实现类去依赖(使用)抽象类或者接口;而不是让接口或抽象类去依赖细节的具体实现类.
依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
依赖倒置原则的主要作用如下。
- 依赖倒置原则可以降低类间的耦合性。
- 依赖倒置原则可以提高系统的稳定性。
- 依赖倒置原则可以减少并行开发引起的风险。
- 依赖倒置原则可以提高代码的可读性和可维护性。
依赖倒置原则的实现方法
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。
- 每个类尽量提供接口或抽象类,或者两者都具备。
- 变量的声明类型尽量是接口或者是抽象类。
- 任何类都不应该从具体类派生。
- 使用继承时尽量遵循里氏替换原则。
下面以“顾客购物程序”为例来说明依赖倒置原则的应用。
【例1】依赖倒置原则在“顾客购物程序”中的应用。
分析:本程序反映了 “顾客类”与“商店类”的关系。商店类中有 sell() 方法,顾客类通过该方法购物以下代码定义了顾客类通过韶关网店 ShaoguanShop 购物:
class Customer {
public void shopping(ShaoguanShop shop) {
//购物
System.out.println(shop.sell());
}
}
但是,这种设计存在缺点,如果该顾客想从另外一家商店(如婺源网店 WuyuanShop)购物,就要将该顾客的代码修改如下:(上面的实现方法写死了店铺地址, 我们需要实现动态实现)
顾客每更换一家商店,都要修改一次代码,这明显违背了开闭原则。存在以上缺点的原因是:顾客类设计时同具体的商店类绑定了,这违背了依赖倒置原则。解决方法是:定义“婺源网店”和“韶关网店”的共同接口 Shop,顾客类面向该接口编程,其代码修改如下:
class Customer {
public void shopping(Shop shop) {
//购物
System.out.println(shop.sell());
}
}
这样,不管顾客类 Customer 访问什么商店,或者增加新的商店,都不需要修改原有代码了。
程序代码如下:
1、接口传递实现:
public class DIPtest {
public static void main(String[] args) {
Customer wang = new Customer();
System.out.println("顾客购买以下商品:");
wang.shopping(new ShaoguanShop());
wang.shopping(new WuyuanShop());
}
}
//商店
interface Shop {
public String sell(); //卖
}
//韶关网店
class ShaoguanShop implements Shop {
public String sell() {
return "韶关土特产:香菇、木耳……";
}
}
//婺源网店
class WuyuanShop implements Shop {
public String sell() {
return "婺源土特产:绿茶、酒糟鱼……";
}
}
//顾客
class Customer {
public void shopping(Shop shop) {
//购物
System.out.println(shop.sell());
}
}
再来一个例子:
public class DependenceInversion02 {
public static void main(String[] args) {
Person person=new Person();
person.getMes(new QQ());
person.getMes(new Message());
}
}
//接收消息的接口;
interface GetMes{
//看看收到的消息;
String show();
}
//短信类;
class Message implements GetMes{
public String show(){
return "收到短信了";
}
}
//QQ消息;
class QQ implements GetMes{
public String show(){
return "收到QQ消息了";
}
}
//具体的人;
class Person{
//接收消息;
public void getMes(GetMes getMes){
System.out.println(getMes.show());
}
}
5、里氏替换原则
里氏替换原则(Liskov Substitution Principle)(LSP)
定义:所有引用基类的地方必须能透明的使用子类对象
就是说当在程序中将一个对象替换成他的子类时,程序可以继续原有的行为,他察觉不出符类和子类的区别。但是反过来却不成立,如果一个程序使用的是一个子类的话,他不一定适用于父类。
以电脑举例:电脑有CPU,电脑就是程序实体,CPU就是它使用的基类,CPU又有子类IntelCpu。
public class Cpu {
public void work(){
System.out.println("CPU在工作");
}
}
public class IntelCpu extends Cpu {
@Override
public void work() {
System.out.println("英特尔CPU工作");
}
}
public class Computer {
private Cpu cpu;
public void run() {
this.cpu.work();
}
public Cpu getCpu() {
return cpu;
}
public void setCpu(Cpu cpu) {
this.cpu = cpu;
}
}
电脑依赖的是父类,此时将Cpu换成Intel类型的,电脑仍能正常工作,它察觉不到任何改变,这符合里氏替换原则。
而反过来,假设现在有一台电脑,它只能使用IntelCpu才能工作。 就是只能子类替代父类而不能父类替代子类。
实例二:
让类A和类B公共去继承同一个类,这样A与B之间的耦合性就降低了;
若类B还想用类A的方法,可使用组合/聚合/依赖的方式将类A的方法调用过来.
public class LiskovSubstitution02 {
public static void main(String[] args) {
System.out.println("类A调用方法");
A a = new A();
System.out.println("11-3=" + a.func1(11, 3));
System.out.println("1-8=" + a.func1(1, 8));
System.out.println("类B调用方法");
B b = new B();
System.out.println("11-3=" + b.func3(11, 3));
System.out.println("1-8=" + b.func3(1, 8));
System.out.println("11+3+9=" + b.func2(11, 3));
}
}
//创建一个公共类;让类A和类B去继承它
class PubClass{
}
//类 A 继承公共类 ;完成两数相减的任务;
class A extends PubClass{
public int func1(int num1, int num2) {
return num1 - num2;
}
}
//类B 继承 公共类; 计划完成两数相加的任务;
class B extends PubClass {
//若类B还想用类A的方法;可使用组合的方式;
private A a=new A();
public int func3(int num1,int num2){
//这里调用的就是类A的方法了;
return this.a.func1(num1, num2);
}
//这里的方法func1就和类A没关系了;
public int func1(int a, int b) {
return a + b;
}
//在这里就是两数相加 + 9 ;
public int func2(int a, int b) {
return func1(a, b) + 9;
}
}
输出:
类A调用方法
11-3=8
1-8=-7
类B调用方法
11-3=8
1-8=-7
11+3+9=23
6、开闭原则(Open Closed Principle)
类,作用域,方法 应该做到 对扩展开放 (扩展开放是对提供方来说的) ;对修改关闭(修改关闭是对于使用方而言;例如说扩展了一个新的功能,使用方的代码不用去修改);
用接口抽象地构建框架;然后通过实现类扩展实现功能.
案例:不同的图形去继承图形类;得到 m_type 属性;
在绘制图形类(使用方)GraphicEditor中,根据不同的 m_type 去绘制图形,而每次要添加新的图形绘制时,需要定义图形后;然后还要在使用方GraphicEditor类中进行定义方法;
public class OpenClosed02 {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());//我要矩形
graphicEditor.drawShape(new Circle());//我要圆形
graphicEditor.drawShape(new Square());//我要正方形
}
}
//绘制图形类;
class GraphicEditor {
//根据不同的 m_type 绘图;
public void drawShape(Shape s) {
s.willDraw();
}
}
//图形类 (父类)==>作为抽象类;
abstract class Shape {
int m_type;
//抽象方法;
public abstract void willDraw();
}
//矩形类,继承图形类
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
public void willDraw() {
System.out.println("我要矩形");
}
}
//圆形类;继承图形类;
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
public void willDraw() {
System.out.println("我要原形");
}
}
//如果需要新添加一个绘图方式;
class Square extends Shape{
Square(){
super.m_type =3;
}
public void willDraw() {
System.out.println("我要正方形");
}
}
7、迪米特法原则
类和类之间的关系越是复杂,他们的耦合性就会越高.
比如说 类 A依赖类B;那么类A就在内部 把类B的逻辑封装起来;不对外泄露信息.
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
只要两个对象之间有耦合关系,那么这两个对象就是朋友关系;
出现成员变量,方法参数,方法返回值中的类为直接的朋友;
只与直接的朋友通信.
一般不要将其他类作为局部变量使用.
这里老师是通过班长这个第三者来清点学生的数量 而不是直接去清点 请求班长执行
public interface ITeacher {
void command(IGroupLeader groupLeader);
}
public class Teacher implements ITeacher {
@Override
public void command(IGroupLeader groupLeader) {
// 班长清点人数
groupLeader.count();
}
}
/**
* 班长类
*/
public interface IGroupLeader {
// 班长清点人数
void count();
}
/**
* 班长类
*/
public class GroupLeader implements IGroupLeader {
private List<Student> students;
public GroupLeader(List<Student> students) {
this.students = students;
}
/**
* 班长清点人数
*/
@Override
public void count() {
// 班长清点人数
System.out.println("上课的学生人数是: " + students.size());
}
}
/**
* 学生类
*/
public interface IStudent {
}
/**
* 学生类
*/
public class Student implements IStudent {
}
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
// 老师类
ITeacher wangTeacher = new Teacher();
List<Student> allStudent = new ArrayList(10);
allStudent.add(new Student());
allStudent.add(new Student());
allStudent.add(new Student());
allStudent.add(new Student());
// 班长
IGroupLeader zhangBanzhang = new GroupLeader(allStudent);
wangTeacher.command(zhangBanzhang);
}
}
减少对外的暴露 Man只需要一个咖啡机 其他操作内部解决
案例二:
分析:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如图 1 所示。
public class LoDtest {
public static void main(String[] args) {
Agent agent = new Agent();
agent.setStar(new Star("林心如"));
agent.setFans(new Fans("粉丝韩丞"));
agent.setCompany(new Company("中国传媒有限公司"));
agent.meeting();
agent.business();
}
}
// 经纪人
class Agent {
private Star myStar;
private Fans myFans;
private Company myCompany;
public void setStar(Star myStar) {
this.myStar = myStar;
}
public void setFans(Fans myFans) {
this.myFans = myFans;
}
public void setCompany(Company myCompany) {
this.myCompany = myCompany;
}
public void meeting() {
System.out.println(myFans.getName() + "与明星" + myStar.getName() + "见面了。");
}
public void business() {
System.out.println(myCompany.getName() + "与明星" + myStar.getName() + "洽淡业务。");
}
}
// 明星
class Star {
private String name;
Star(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// 粉丝
class Fans {
private String name;
Fans(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// 媒体公司
class Company {
private String name;
Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
8、合成复用原则Composite Reuse Principle
尽量不要使用继承关系
如果说仅仅是让类B去使用类A的方法,这时使用继承,会让A类和B类之间的耦合性增强
方式一:
方式二:
A 类聚合到B类
方式三:
A组合到B类中
2、常用设计模式
创建型模式:(描述怎样去创建一个对象,创建和使用分离)
● 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式:(描述如何将类或对象安装某种类型组成更大的结构)
● 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式:(描述类和对象如何可以相互协作)
● 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
1、单例模式
核心作用:保证一个类只有一个实例,并且提供一个访问该实例的 全局访问点
比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session 对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory就够,这是就会使用到单例模式。
饿汉式单例模式:
执行步骤:
- 构造器私有化 (防止 new )
- 类的内部创建对象
- 向外暴露一个静态的公共方法。getInstance
- 代码实现
// 饿汉式单例
public class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
// 单例模式核心思想:构造器私有
private Hungry(){
}
// 转载类的时候就完了实例化 一直存在 静态常量法
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
静态代码块懒汉式
// 饿汉式单例
public class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
// 单例模式核心思想:构造器私有
private Hungry(){
}
// 静态代码块创建单例
private static Hungry HUNGRY;
static{
HUNGRY = new Hungry();
}
public static Hungry getInstance(){
return HUNGRY;
}
}
DCL懒汉式单例模式:
// 懒汉式单例 线程安全的情况下
public class LazyMan {
private LazyMan() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static LazyMan lazyMan; // volatile 为了避免指令重排
// 双重检测锁模式的懒汉式单例 DCL 懒汉式
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();// 不是一个原子性操作
/*
1、分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
123
132 A
B // 此时B线程进来会认为lazyman不为null
// 直接返回 此时lazyman 还没有完成构造
// 为了避免指令重排
*/
}
}
}
return lazyMan;
}
//
// public static LazyMan getInstance() {
// if (lazyMan == null) {
// lazyMan = new LazyMan();
// }
// return lazyMan;
// }
// 单线程下确实单例ok,但是多线程并发
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyMan.getInstance();
}).start();
}
}
}
优点:解决了线程不安全问题
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行 同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例, 直接return就行了。方法进行同步效率太低
反射 可以破环这种单例
// 懒汉式单例
// 道高一尺,魔高一丈
public class LazyMan {
private static boolean qinjiang = false;
private LazyMan() {
if(qinjiang == false){
qinjiang=true;
}else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
// synchronized (LazyMan.class){
// if (lazyMan!=null){
// throw new RuntimeException("不要试图使用反射破坏异常");
// }
// }
// System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static LazyMan lazyMan; // volatile 为了避免指令重排多线程的情况下防止线程相互影响的
// 双重检测锁模式的懒汉式单例 DCL 懒汉式
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();// 不是一个原子性操作
/*
1、分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
123
132 A
B // 此时B线程进来会认为lazyman不为null
// 直接返回 此时lazyman 还没有完成构造
// 为了避免指令重排
*/
}
}
}
return lazyMan;
}
// 单线程下确实单例ok,但是多线程并发
public static void main(String[] args) throws Exception {
// 反射 可以破环这种单例
// LazyMan instance = LazyMan.getInstance();
Field qinjiang = LazyMan.class.getDeclaredField("qinjiang");
qinjiang.setAccessible(true);
Constructor<LazyMan> declaredConstructor =LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有构造器
LazyMan instance=declaredConstructor.newInstance();
qinjiang.set(instance,false);
LazyMan instance2=declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
静态内部类
// 静态内部类实现单例模式 不安全
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
单例不安全,因为有反射
优缺点:
- 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
- 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的 实例化。
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
- 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
- 结论:推荐使用
枚举
// enum 本身也是一个 class 类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
//Constructor<EnumSingle> declaredConstructor=EnumSingle.class.getDeclaredConstructor(null);
// 枚举没有无参构造,只有有参构造
Constructor<EnumSingle> declaredConstructor=EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
// java.lang.NoSuchMethodException: com.kuang.single.EnumSingle.<init> 没有空参的构造方法
// java.lang.IllegalArgumentException: Cannot reflectively create enum objects 反射不能破坏枚举的单例
System.out.println(instance1);
System.out.println(instance2);
}
}
枚举没有无参构造,只有有参构造
优缺点:
- 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
- 这种方式是Effective Java作者Josh Bloch 提倡的方式
- 结论:推荐使用
单例模式在JDK应用的源码分析
- 我们JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)
- 代码分析+Debug源码+代码说明
单例模式注意事项和细节说明
- 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。
2、工厂模式
1、作用
- 实现了创建者和调用者的分离
- 详细分类:
- 简单工厂模式:用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
- 工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品)
- 抽象工厂模式:围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
我们常用的工厂模式主要使用简单工厂模式 抽象工厂模式比较少用
工厂设计模式的原则(OOP七大原则):
- 开闭原则:一个软件的实体应当对扩展开放,对修改关闭
- 依赖倒转原则:要针对接口编程,不要针对实现编程
- 迪米特法则:只与你直接的朋友通信,而避免和陌生人通信
核心本质:
- 实例化对象不使用new,用工厂方法代替 factory
- 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦
2、简单工厂模式(静态工厂模式)
用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
public interface Car {
void name();
}
public class WuLing implements Car{
@Override
public void name() {
System.out.println("五菱宏光");
}
}
public class Tesla implements Car{
@Override
public void name() {
System.out.println("特斯拉");
}
}
// 静态工厂模式
// 开闭原则
public class CarFactory {
// 方法一: 不满足开闭原则
public static Car getCar(String car){
if(car.equals("wuling")){
return new WuLing();
}else if(car.equals("tesila")){
return new Tesla();
}else {
return null;
}
}
// 方法二:
public static Car geyWuling(){
return new WuLing();
}
public static Car geyTesla(){
return new Tesla();
}
}
public class Consumer {
public static void main(String[] args) {
// 接口,所有的实现类
// Car car = new WuLing();
// Car car1 = new Tesla();
// 2、使用工厂创建
Car car = CarFactory.getCar("wuling");
Car car1 = CarFactory.getCar("tesila");
car.name();
car1.name();
}
}
弊端:
增加一个新的产品,做不到不修改代码。
来一个披萨订购的demo
/**
* 披萨接口
*/
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 String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class GreekPizza extends Pizza{
@Override
public void prepare() {
System.out.println(" 给希腊披萨 准备原材料!");
}
}
public class CheesePizza extends Pizza{
@Override
public void prepare() {
System.out.println(" 给制作奶酪披萨 准备原材料!");
}
}
/**
订购工厂
**/
public class OrderPizza {
// 构造器
public OrderPizza() {
Pizza pizza = null;
String orderType; // 订购披萨类型
do {
orderType = getType();
if (orderType.equals("greek")){
pizza = new GreekPizza();
pizza.setName("greek");
}else if (orderType.equals("cheese")){
pizza = new CheesePizza();
pizza.setName("cheese");
}else {
break;
}
// 输出披萨制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}while (true);
}
// 写一个方法 来获取用户希望订购的披萨种类
private String getType(){
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
return next;
}
}
/**
* 客户端
*/
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza();
}
}
违反了设计模式的ocp原则,即 对扩展开发 对修改关闭 当我们要增加新功能的时候要修改代码
修改一些代码:
import java.util.Scanner;
public class OrderPizza {
// 定义一个简单工厂
SimpleFactory simpleFactory;
Pizza pizza = null;
public void setSimpleFactory(SimpleFactory simpleFactory){
String orderType = null;
this.simpleFactory = simpleFactory;
do {
orderType = getType();
pizza = simpleFactory.createPizza(orderType);
if (pizza != null){
pizza.setName(orderType);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
}while (true);
}
// 写一个方法 来获取用户希望订购的披萨种类
private String getType(){
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
return next;
}
}
// 加一个管理全部pizza的工厂
public class SimpleFactory {
public Pizza createPizza(String orderType){
Pizza pizza = null;
System.out.println("使用了简单工厂模式");
if (orderType.equals("greek")){
pizza = new GreekPizza();
pizza.setName("greek");
}else if (orderType.equals("cheese")){
pizza = new CheesePizza();
pizza.setName("cheese");
}
return pizza;
}
}
3、工厂方法模式:
用来生产同一等级结构中的固定产品(支持增加任意产品)
public interface Car {
void name();
}
public class WuLing implements Car {
@Override
public void name() {
System.out.println("五菱宏光");
}
}
public class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
// 工厂方法模式
public interface CarFactory {
Car getCar();
}
public class WulingFactory implements CarFactory{
@Override
public Car getCar() {
return new WuLing();
}
}
public class TeslaFactory implements CarFactory{
@Override
public Car getCar() {
return new Tesla();
}
}
public class Consumer {
public static void main(String[] args) {
Car car = new WulingFactory().getCar();
Car car1 = new TeslaFactory().getCar();
car.name();
car1.name();
Car car2 = new MoBaiFactory().getCar();
car2.name();
}
}
对比简单工厂模式
1、结构复杂度:simple>method
2、代码复杂度:simple>method
3、编程复杂度:simple>method
4、管理上的复杂度:simple>method
根据设计原则,使用工厂方法模式;根据实际业务,使用简单工厂模式。
3、原型模式
1、模式介绍
- 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
原型模式的定义和特点
原型(Prototype)模式的定义如下:
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多,这里不一一列举了。
优点:
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
原型模式结构和实现
由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
2、 模式的结构
原型模式包含以下主要角色。
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
3、模式的实现
原型模式的克隆分为浅克隆和深克隆。
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的的
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。其代码如下:
// 具体原型类
class Realizetype implements Cloneable {
Realizetype() {
System.out.println("具体原型创建成功!");
}
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype) super.clone();
}
}
// 原型模式的测试类
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype obj1 = new Realizetype();
Realizetype obj2 = (Realizetype) obj1.clone();
System.out.println("obj1==obj2?" + (obj1 == obj2));
}
}
实例:
public class Sheep implements Cloneable{
private String name;
private int age;
private String color;
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 + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
传统方法(不适用原型模式)
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "白色");
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
System.out.println(sheep.hashCode());// 1735600054
System.out.println(sheep2.hashCode());// 21685669
}
原型模式
public static void main(String[] args) throws CloneNotSupportedException {
Sheep sheep = new Sheep("tom", 1, "白色");
Sheep cloneSheep = (Sheep) sheep.clone();
System.out.println(sheep.hashCode());// 1735600054
System.out.println(cloneSheep.hashCode());// 21685669
}
通过上述两种方式的对比,我们发现不管使用哪种方式,拷贝的方式都是深拷贝,那对于对象中的属性对象是什么拷贝呢?向 Sheep类中加入一个对象属性
private String name;
private int age;
private String color;
public Sheep friend; //是对象, 克隆是会如何处理
.............
public static void main(String[] args) throws CloneNotSupportedException {
Sheep sheep = new Sheep("tom", 1, "白色");
sheep.friend = new Sheep("jerry", 2, "黑色");
Sheep cloneSheep = (Sheep) sheep.clone();
// 哈希值:1735600054 sheep.friend 21685669
System.out.println(sheep.hashCode()+" sheep.friend"+" "+sheep.friend.hashCode());
// 哈希值:2133927002 cloneSheep.friend 21685669
System.out.println(cloneSheep.hashCode()+" cloneSheep.friend"+" "+cloneSheep.friend.hashCode());
}
经测试,我们发现,对于类中属性对象,采用的是浅拷贝方式,那有什么方法能让他实现深拷贝呢?有下面两种方法
- 通过重写clone()方法实现
- 通过序列化实现
一、通过重写clone()方法实现深拷贝
3. 修改上述 Sheep类的克隆方法
@Override
protected Sheep clone() throws CloneNotSupportedException {
Sheep sheep = null;
// 先克隆Sheep对象
sheep = (Sheep) super.clone();
// 再克隆Sheep中的friend对象
sheep.friend = friend.getclone();
return sheep;
}
// 为对象中的对象即friend提供克隆方法
protected Sheep getclone() throws CloneNotSupportedException {
return (Sheep) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Sheep sheep = new Sheep("tom", 1, "白色");
sheep.friend = new Sheep("jerry", 2, "黑色");
Sheep cloneSheep = (Sheep) sheep.clone();
// 1735600054 sheep.friend 21685669
System.out.println(sheep.hashCode()+" sheep.friend"+" "+sheep.friend.hashCode());
// 2133927002 cloneSheep.friend 1836019240
System.out.println(cloneSheep.hashCode()+" cloneSheep.friend"+" "+cloneSheep.friend.hashCode());
}
4、深拷贝
深拷贝通过多种方法实现
方式一: 通过clone方法实现深拷贝
public class DeepProtoType implements Serializable, Cloneable {
public String name;
public DeepCloneableTarget deepCloneableTarget;
public DeepProtoType() {
super();
}
// 深拷贝 方式1 使用clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
// 这里完成基本数据类型和String的克隆
deep = super.clone();
// 对引用类型进行单独处理
DeepProtoType deepProtoType = (DeepProtoType) deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deepProtoType;
}
}
方式二: 通过序列化来实现深拷贝
public class DeepProtoType implements Serializable, Cloneable {
public String name;
public DeepCloneableTarget deepCloneableTarget;
public DeepProtoType() {
super();
}
// 方式二 利用对象的 序列化来实现 推荐使用
public Object deepClone(){
// 创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
DeepProtoType deepProtoType = null;
try {
// 序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 对当前的对象以对象流的方式输出
// 反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
deepProtoType = (DeepProtoType) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return deepProtoType;
}
}
5、在Spring框架的使用
- Spring中原型bean的创建,就是原型模式的应用
- 代码分析+Debug源码
这里的scope是指定创建bean的方法 默认是单例模式,这里指定的是原型模式prototype
4、建造者模式
1、建造者概述
在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。例如,计算机是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。
生活中这样的例子很多,如游戏中的不同角色,其性别、个性、能力、脸型、体型、服装、发型等特性都有所差异;还有汽车中的方向盘、发动机、车架、轮胎等部件也多种多样;每封电子邮件的发件人、收件人、主题、内容、附件等内容也各不相同。
以上所有这些产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式描述,只有建造者模式可以很好地描述该类产品的创建。
建造者(Builder)模式的定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
该模式的主要优点如下:
- 封装性好,构建和表示分离。
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
- 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
其缺点如下:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
2、模式结构
建造者(Builder)模式的主要角色如下。
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
3、建造者含指挥
角色分析
代码实现
package com.wen.builder;
//产品,房子
public class Product {
private String builderA;
private String builderB;
private String builderC;
private String builderD;
public String getBuilderA() {
return builderA;
}
public void setBuilderA(String builderA) {
this.builderA = builderA;
}
public String getBuilderB() {
return builderB;
}
public void setBuilderB(String builderB) {
this.builderB = builderB;
}
public String getBuilderC() {
return builderC;
}
public void setBuilderC(String builderC) {
this.builderC = builderC;
}
public String getBuilderD() {
return builderD;
}
public void setBuilderD(String builderD) {
this.builderD = builderD;
}
@Override
public String toString() {
return "Product{" +
"builderA='" + builderA + '\'' +
", builderB='" + builderB + '\'' +
", builderC='" + builderC + '\'' +
", builderD='" + builderD + '\'' +
'}';
}
}
Builder
package com.wen.builder;
//抽象的建造者:方法
public abstract class Builder {
abstract void builderA();//地基
abstract void builderB();//钢筋工程
abstract void builderC();//铺电线
abstract void builderD();//粉刷
//完工:得到产品
abstract Product getProduct();
}
Worker
package com.wen.builder;
// 具体的建造者:工人
public class Worker extends Builder {
private Product product;
public Worker() {
product = new Product();
}
@Override
void builderA() {
product.setBuilderA("地基");
System.out.println("地基");
}
@Override
void builderB() {
product.setBuilderB("钢筋工程");
System.out.println("钢筋工程");
}
@Override
void builderC() {
product.setBuilderC("铺电线");
System.out.println("铺电线");
}
@Override
void builderD() {
product.setBuilderD("粉刷");
System.out.println("粉刷");
}
@Override
Product getProduct() {
return product;
}
}
Director
package com.wen.builder;
// 指挥:核心,负责指挥构建一个工程,工程如何构建,由它决定
public class Director {
//指挥工人按照顺序建房子
public Product build(Builder builder){
builder.builderA();
builder.builderB();
builder.builderC();
builder.builderD();
return builder.getProduct();
}
}
Test
package com.wen.builder;
public class Test {
public static void main(String[] args) {
// 指挥
Director director = new Director();
// 指挥具体的工人完成产品
Product build = director.build(new Worker());
System.out.println(build.toString());
}
}
1、建造者模式的意图和适用场景(拓展)
建造者模式唯一区别于工厂模式的是针对复杂对象的创建。也就是说,如果创建简单对象,通常都是使用工厂模式进行创建,而如果创建复杂对象,就可以考虑使用建造者模式。
当需要创建的产品具备复杂创建过程时,可以抽取出共性创建过程,然后交由具体实现类自定义创建流程,使得同样的创建行为可以生产出不同的产品,分离了创建与表示,使创建产品的灵活性大大增加。
建造者模式主要适用于以下应用场景:
- 相同的方法,不同的执行顺序,产生不同的结果。
- 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
- 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
- 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。
2、建造者的参与者(拓展)
3、举例说明:设计房屋(拓展)
4、建造者无指挥
Product
package com.wen.builder.demo02;
//产品:套餐
public class Product {
private String BuildA ="汉堡";
private String BuildB ="可乐";
private String BuildC ="薯条";
private String BuildD ="甜点";
public String getBuildA() {
return BuildA;
}
public void setBuildA(String buildA) {
BuildA = buildA;
}
public String getBuildB() {
return BuildB;
}
public void setBuildB(String buildB) {
BuildB = buildB;
}
public String getBuildC() {
return BuildC;
}
public void setBuildC(String buildC) {
BuildC = buildC;
}
public String getBuildD() {
return BuildD;
}
public void setBuildD(String buildD) {
BuildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"BuildA='" + BuildA + '\'' +
", BuildB='" + BuildB + '\'' +
", BuildC='" + BuildC + '\'' +
", BuildD='" + BuildD + '\'' +
'}';
}
}
Builder
package com.wen.builder.demo02;
//建造者
public abstract class Builder {
abstract Builder BuildA(String msg);//"汉堡";
abstract Builder BuildB(String msg);//"可乐";
abstract Builder BuildC(String msg);//"薯条";
abstract Builder BuildD(String msg);//"甜点";
abstract Product getProduct();
}
Worker
package com.wen.builder.demo02;
public class Worker extends Builder {
private Product product;
public Worker() {
product = new Product();
}
@Override
Builder BuildA(String msg) {
product.setBuildA(msg);
return this;
}
@Override
Builder BuildB(String msg) {
product.setBuildB(msg);
return this;
}
@Override
Builder BuildC(String msg) {
product.setBuildC(msg);
return this;
}
@Override
Builder BuildD(String msg) {
product.setBuildD(msg);
return this;
}
@Override
Product getProduct() {
return product;
}
}
Test
package com.wen.builder.demo02;
public class Test {
public static void main(String[] args) {
//服务员
Worker worker = new Worker();
//可以按默认走,也可以自由组合
Product product = worker.BuildA("全家桶").BuildB("雪碧").getProduct();
System.out.println(product.toString());
}
}
Product{BuildA=‘全家桶’, BuildB=‘雪碧’, BuildC=‘薯条’, BuildD=‘甜点’}
5、建造者模式和工厂模式的区别
通过前面的学习,我们已经了解了建造者模式,那么它和工厂模式有什么区别呢?
- 建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
- 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样
- 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
- 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。
6、建造者模式在JDK的应用
建造者模式在JDK中的StringBuilder中使用了建造者模式的方法。
Builder的实现如上图所示。
- Appendable定义了多个append方法,属于抽象方法,就是抽象建造者相当于上面的Builder类。
- AbstractStringBuilder实现了Appendable接口方法,这里已经是建造者了不能实例化 相当于worker
- StringBuilder充当指挥着角色,同时充当具体的建造者,建造方法的实现是有AbstractStringBuilder的完成。
这个可能和标准的建造者模式不太一样,思想大同小异。
5、适配器模式
1、适配器模式概述
在现实生活中,经常出现两个对象因接口不兼容而不能在一起工作的实例,这时需要第三者进行适配。例如,讲中文的人同讲英文的人对话时需要一个翻译,用直流电的笔记本电脑接交流电源时需要一个电源适配器,用计算机访问照相机的 SD 内存卡时需要一个读卡器等。
在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。
适配器理解:
2、适配器模式优缺点
适配器模式(Adapter)的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
该模式的主要优点如下。
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
- 在很多业务场景中符合开闭原则。
其缺点是:
- 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
- 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。
3、模式的结构
适配器模式(Adapter)包含以下主要角色。
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
接口
4、类适配器模式
的代码如下。
package adapter;
// 目标接口
interface Target
{
public void request();
}
// 适配者接口
class Adaptee
{
public void specificRequest()
{
System.out.println("适配者中的业务代码被调用!");
}
}
// 类适配器类
// 这里直接继承了适配器
class ClassAdapter extends Adaptee implements Target
{
public void request()
{
specificRequest();
}
}
// 客户端代码
public class ClassAdapterTest
{
public static void main(String[] args)
{
System.out.println("类适配器模式测试:");
Target target = new ClassAdapter();
target.request();
}
}
电压转接
// 需要的方法,适配器类
public class Voltage220V {
public int output220V(){
int src = 220;
System.out.println("电压:"+src+"伏");
return src;
}
}
// 目标接口
public interface IVoltage5V {
// 输出5伏
public int output5V();
}
/**
* 适配器就是一个转接口 需要实现多个方法
*/
public class VoltageAdapter extends Voltage220V implements IVoltage5V{
@Override
public int output5V() {
// 获取到220v的电压
int src = output220V();
int dstV = src / 44; // 转成5伏
return dstV;
}
}
public class Phone {
// 充电的方法
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.output5V() == 5){
System.out.println("电压为5伏,可以充电!!");
}else if (iVoltage5V.output5V() > 5){
System.out.println("电压过高,不能充电!!");
}
}
}
public class Client {
public static void main(String[] args) {
System.out.println("=====类适配器模式=====");
Phone phone = new Phone();
// 只需要一个适配器
phone.charging(new VoltageAdapter());
}
}
5、对象适配器模式
代码如下。
package adapter;
// 对象适配器类
class ObjectAdapter implements Target
{
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee)
{
this.adaptee=adaptee;
}
public void request()
{
adaptee.specificRequest();
}
}
// 客户端代码
public class ObjectAdapterTest
{
public static void main(String[] args)
{
System.out.println("对象适配器模式测试:");
Adaptee adaptee = new Adaptee();
Target target = new ObjectAdapter(adaptee);
target.request();
}
}
package adapter;
// 待使用的类
// 目标:发动机
interface Motor
{
public void drive();
}
// 定义某种发动机对象(相当于Motor的必须类,只有适配器存在这个类才能实现)
// 适配者1:电能发动机
class ElectricMotor
{
public void electricDrive()
{
System.out.println("电能发动机驱动汽车!");
}
}
// 适配者2:光能发动机
class OpticalMotor
{
public void opticalDrive()
{
System.out.println("光能发动机驱动汽车!");
}
}
// 下面两个相当于继承了发动机的功能但是只需要插入一个特定的发动机对象
// 适配器实现发动机的所有功能但是差一个发动机对象驱动 所以需要组合一个发动机对象
// 电能适配器
class ElectricAdapter implements Motor
{
private ElectricMotor emotor;
public ElectricAdapter()
{
emotor=new ElectricMotor();
}
public void drive()
{
emotor.electricDrive();
}
}
// 光能适配器
class OpticalAdapter implements Motor
{
private OpticalMotor omotor;
public OpticalAdapter()
{
omotor=new OpticalMotor();
}
public void drive()
{
omotor.opticalDrive();
}
}
// 客户端代码
public class MotorAdapterTest
{
public static void main(String[] args)
{
System.out.println("适配器模式测试:");
Motor motor=(Motor)ReadXML.getObject();
motor.drive();
}
}
对象适配器就是不会继承类,解决兼容问题,而是改进进行聚合来实现。
电压变压实例
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V;
@Override
public int output5V() {
// 获取到220v的电压
int src = voltage220V.output220V();
int dstV = src / 44; // 转成5伏
return dstV;
}
}
// 就将适配器进行修改 不再泛化 而是进行聚合 聚合一个属性来实现
// 下面可以进行进行构造器来实现依赖
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V;
@Override
public int output5V() {
// 获取到220v的电压
int dstV = 0;
if (voltage220V != null){
int src = voltage220V.output220V();
dstV = src / 44; // 转成5伏
}
return dstV;
}
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
}
6、接口适配器模式
7、适配器在框架SpringMVC使用
- SpringMVC中的HandlerAdapter, 就使用了适配器模式
- SpringMVC处理请求的流程回顾
- 使用HandlerAdapter 的原因分析:
可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样违背了OCP原则。
Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类
- 适配器代替controller执行相应的方法
- 扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了,
- 这就是设计模式的力量
6、代理模式
1、代理模式的概念
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
在软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
其主要缺点是:
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
代理模式的结构:
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
为什么要学习代理模式?
因为这是SpringAOP的底层!!!!!
代理模式的分类:
- 静态代理
- 动态代理
2、静态代理
角色分析:
- 抽象角色:一般使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真角色,代理真实角色后,会做一些附属操作
- 客户:访问代理的人
代理模式的优点:
- 可以使角色的操作更加纯粹!不用去关注!
- 公共也就交给代理角色!实现业务分工!
- 公共业务发生扩展的时候,更加集中管理!
缺点:
- 一个真实角色需要产生一个代理角色;代码量会翻倍~开发效率低下。
静态代理的实例:
package proxy;
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.Request();
}
}
//抽象主题
interface Subject {
void Request();
}
//真实主题
class RealSubject implements Subject {
public void Request() {
System.out.println("访问真实主题方法...");
}
}
//代理
class Proxy implements Subject {
private RealSubject realSubject;
public void Request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.Request();
postRequest();
}
public void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}
public void postRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}
运行结果:
访问真实主题之前的预处理。
访问真实主题方法…
访问真实主题之后的后续处理。
典型的实现用户业务CURD
创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查
//抽象角色:增删改查业务
public interface UserService {
void add();
void delete();
void update();
void query();
}
我们需要一个真实对象来完成这些增删改查操作
//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
需求来了,现在我们需要增加一个日志功能,怎么实现!
- 思路1 :在实现类上增加代码 【麻烦!】
- 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!
设置一个代理类来处理日志!代理角色
//代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
测试类
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//代理类
UserServiceProxy proxy = new UserServiceProxy();
//使用代理类实现日志功能!
proxy.setUserService(userService);
proxy.add();
}
}
但是静态代理会产生代码冗余,添加一个对象就要实现一个类的实现。
3、动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的!
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
○ 基于接口----JDK代理
○ 基于类:cglib
○ Java字节码实现:javasist
需要了解两个类:Proxy代理类,InvocationHandler接口调用处理程序(里面会使用invoke方法)
动态代理也叫 JDK 代理或接口代理,有以下特点:
- 代理对象不需要实现接口
- 代理对象的生成是利用 JDK 的 API 动态的在内存中构建代理对象
- 能在代码运行时动态地改变某个对象的代理,并且能为代理对象动态地增加方法、增加行为
一般情况下,动态代理的底层不用我们亲自去实现,可以使用线程提供的 API 。例如,在 Java 生态中,目前普遍使用的是 JDK 自带的代理和 GGLib 提供的类库。
注意该方法在 Proxy 类中是静态方法,且接收的三个参数说明依次为:
- ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
- Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
- InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,把当前执行目标对象的方法作为参数传入
Object invoke(Object proxy, 方法 method, Object[] args);
// 参数
// proxy - 调用该方法的代理实例
// method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
// args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
动态代理的实现
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
典型出租房屋动态代理
Rent.java 即抽象角色
// 抽象角色:租房
public interface Rent {
public void rent();
}
Host.java 即真实角色 只有真实角色才需要实现接口
// 真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
ProxyInvocationHandler.java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
// proxy : 代理类 method : 代理类的调用处理程序的方法对象.
// 处理代理实例上的方法调用并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:本质利用反射实现!
Object result = method.invoke(rent, args);
fare();
return result;
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client . java
// 租客
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理实例的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host); //将真实角色放置进去!
Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
proxy.rent();
}
}
我们还可以实现一下CURD操作
我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
代理类
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// proxy : 代理类
// method : 代理类的调用处理程序的方法对象.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String methodName){
System.out.println("执行了"+methodName+"方法");
}
}
测试类
public class Test {
public static void main(String[] args) {
//真实对象
UserServiceImpl userService = new UserServiceImpl();
//代理对象的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
proxy.delete();
}
}
动态代理应用于AOP切面编程,为了避免对大量的源代码更改,所以增加一个切面编程,可以横切插入数据就上面的invoke方法中在Object result = method.invoke(target, args); 上下行可以进行添加操作,实现对原先的代码进行添加等等,广泛用于log的打印。
采用动态代理基本上只要是人(IPerson)就可以提供找老师服务。
顶层接口
public interface IPerson {
void findTeacher(); //找老师
}
创建辅导班代理类(辅导班相当于一个中介 将老师和学生连接起来)
public class JdkFuDao implements InvocationHandler {
private IPerson target;
public IPerson getInstance(IPerson target) {
this.target = target;
Class<?> clazz = target.getClass();
return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(this.target, args);
after();
return result;
}
private void after() {
System.out.println("双方同意,开始辅导");
}
private void before() {
System.out.println("这里是C语言中文网辅导班,已经收集到您的需求,开始挑选老师");
}
}
创建一个人的类 目标对象一定要实现接口
public class ZhaoLiu implements IPerson {
@Override
public void findTeacher() {
System.out.println("符合赵六的要求");
}
public void buyInsure() {
}
}
测试类
public class Test {
public static void main(String[] args) {
JdkFuDao jdkFuDao = new JdkFuDao();
IPerson zhaoliu = jdkFuDao.getInstance(new ZhaoLiu());
zhaoliu.findTeacher();
}
}
4、静态代理和动态代理区别
静态代理和动态代理主要有以下几点区别:
- 静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,则代理类需要同步增加,违背开闭原则。
- 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
- 若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
7、模板方法模式
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。
1、模式的定义和特点
模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
该模式的主要优点如下。
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
该模式的主要缺点如下。
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
- 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
2、模式的结构和实现
模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。现在来介绍它们的基本结构。
模式的结构
模板方法模式包含以下主要角色。
1)抽象类/抽象模板(Abstract Class)
抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
- 抽象方法:在抽象类中声明,由具体子类实现。
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
2)具体子类/具体实现(Concrete Class)
具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
模板方法模式的结构图如图 1 所示。
模式的实现
public class TemplateMethodPattern {
public static void main(String[] args) {
AbstractClass tm = new ConcreteClass();
tm.TemplateMethod();
}
}
//抽象类
abstract class AbstractClass {
//模板方法
public void TemplateMethod() {
SpecificMethod();
abstractMethod1();
abstractMethod2();
}
//具体方法
public void SpecificMethod() {
System.out.println("抽象类中的具体方法被调用...");
}
//抽象方法1
public abstract void abstractMethod1();
//抽象方法2
public abstract void abstractMethod2();
}
//具体子类
class ConcreteClass extends AbstractClass {
public void abstractMethod1() {
System.out.println("抽象方法1的实现被调用...");
}
public void abstractMethod2() {
System.out.println("抽象方法2的实现被调用...");
}
}
3、模式的应用
【例1】用模板方法模式实现出国留学手续设计程序。
分析:出国留学手续一般经过以下流程:索取学校资料,提出入学申请,办理因私出国护照、出境卡和公证,申请签证,体检、订机票、准备行装,抵达目标学校等,其中有些业务对各个学校是一样的,但有些业务因学校不同而不同,所以比较适合用模板方法模式来实现。
在本实例中,我们先定义一个出国留学的抽象类 StudyAbroad,里面包含了一个模板方法 TemplateMethod(),该方法中包含了办理出国留学手续流程中的各个基本方法,其中有些方法的处理由于各国都一样,所以在抽象类中就可以实现,但有些方法的处理各国是不同的,必须在其具体子类(如美国留学类 StudyInAmerica)中实现。如果再增加一个国家,只要增加一个子类就可以了,图 2 所示是其结构图。
public class StudyAbroadProcess {
public static void main(String[] args) {
StudyAbroad tm = new StudyInAmerica();
tm.TemplateMethod();
}
}
//抽象类: 出国留学
abstract class StudyAbroad {
public void TemplateMethod() //模板方法
{
LookingForSchool(); //索取学校资料
ApplyForEnrol(); //入学申请
ApplyForPassport(); //办理因私出国护照、出境卡和公证
ApplyForVisa(); //申请签证
ReadyGoAbroad(); //体检、订机票、准备行装
Arriving(); //抵达
}
public void ApplyForPassport() {
System.out.println("三.办理因私出国护照、出境卡和公证:");
System.out.println(" 1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。");
System.out.println(" 2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。");
}
public void ApplyForVisa() {
System.out.println("四.申请签证:");
System.out.println(" 1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;");
System.out.println(" 2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。");
}
public void ReadyGoAbroad() {
System.out.println("五.体检、订机票、准备行装:");
System.out.println(" 1)进行身体检查、免疫检查和接种传染病疫苗;");
System.out.println(" 2)确定机票时间、航班和转机地点。");
}
public abstract void LookingForSchool();//索取学校资料
public abstract void ApplyForEnrol(); //入学申请
public abstract void Arriving(); //抵达
}
//具体子类: 美国留学
class StudyInAmerica extends StudyAbroad {
@Override
public void LookingForSchool() {
System.out.println("一.索取学校以下资料:");
System.out.println(" 1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;");
System.out.println(" 2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;");
System.out.println(" 3)了解该学校的住宿、交通、医疗保险情况如何;");
System.out.println(" 4)该学校在中国是否有授权代理招生的留学中介公司?");
System.out.println(" 5)掌握留学签证情况;");
System.out.println(" 6)该国政府是否允许留学生合法打工?");
System.out.println(" 8)毕业之后可否移民?");
System.out.println(" 9)文凭是否受到我国认可?");
}
@Override
public void ApplyForEnrol() {
System.out.println("二.入学申请:");
System.out.println(" 1)填写报名表;");
System.out.println(" 2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;");
System.out.println(" 3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。");
}
@Override
public void Arriving() {
System.out.println("六.抵达目标学校:");
System.out.println(" 1)安排住宿;");
System.out.println(" 2)了解校园及周边环境。");
}
}
一、索取学校以下资料:
1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;
2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;
3)了解该学校的住宿、交通、医疗保险情况如何;
4)该学校在中国是否有授权代理招生的留学中介公司?
5)掌握留学签证情况;
6)该国政府是否允许留学生合法打工?
8)毕业之后可否移民?
9)文凭是否受到我国认可?
二、入学申请:
1)填写报名表;
2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;
3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。
三、办理因私出国护照、出境卡和公证:
1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。
2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。
四.申请签证:
1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;
2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。
五、体检、订机票、准备行装:
1)进行身体检查、免疫检查和接种传染病疫苗;
2)确定机票时间、航班和转机地点。
六、抵达目标学校:
1)安排住宿;
2)了解校园及周边环境。
应用2:
编写制作豆浆的程序,说明如下:
- 制作豆浆的流程 选材—>添加配料—>浸泡—>放到豆浆机打碎
- 通过添加不同的配料,可以制作出不同口味的豆浆
- 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
- 请使用 模板方法模式 完成 (说明:因为模板方法模式,比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式 )
编写制作豆浆的程序,说明如下:
- 制作豆浆的流程 选材–>添加配料—>浸泡—>放到豆浆机打碎
- 通过添加不同的配料,可以制作出不同口味的豆浆
- 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红豆、花生豆浆。。。)
模板方法模式的钩子方法
- 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
- 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造
package cn.wen.template;
// 抽象类 表示豆浆
public abstract class SoyaMilk {
//模板方法,make 方法可以做成final
final void make() {
select();
if (customerWantCondiments()){
addCondiments();
}
soak();
beat();
}
// 选材料
public void select(){
System.out.println("第一步:选择好新鲜的黄豆");
}
// 添加不同的配料 子类具体实现
public abstract void addCondiments();
// 黄豆和配料就跑
public void soak(){
System.out.println("第三步:黄豆和配料开始浸泡,需要3小时");
}
// 打碎
public void beat(){
System.out.println("第四步:黄豆和配料放到豆浆机打碎");
}
// 钩子方法,决定是否需要添加配料
public boolean customerWantCondiments(){
return true;
}
}
package cn.wen.template;
public class PeanutSoyaMilk extends SoyaMilk{
@Override
public void addCondiments() {
System.out.println("加入上好的花生");
}
}
package cn.wen.template;
public class PureSoyaMilk extends SoyaMilk{
@Override
public void addCondiments() {
// 空实现
}
// 不需要添加配料
@Override
public boolean customerWantCondiments() {
return false;
}
}
package cn.wen.template;
public class RedBeanSoyaMilk extends SoyaMilk{
@Override
public void addCondiments() {
System.out.println("加入上好的红豆");
}
}
package cn.wen.template;
public class Client {
public static void main(String[] args) {
// 制作红豆豆浆
System.out.println("----制作红豆豆浆-----");
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
System.out.println("----制作红豆豆浆-----");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
redBeanSoyaMilk.make();
}
}
4、模式的扩展
在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。如下面例子中,可以通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果,其结构图如图 3 所示。
public class HookTemplateMethod {
public static void main(String[] args) {
HookAbstractClass tm = new HookConcreteClass();
tm.TemplateMethod();
}
}
//含钩子方法的抽象类
abstract class HookAbstractClass {
//模板方法
public void TemplateMethod() {
abstractMethod1();
HookMethod1();
if (HookMethod2()) {
SpecificMethod();
}
abstractMethod2();
}
//具体方法
public void SpecificMethod() {
System.out.println("抽象类中的具体方法被调用...");
}
//钩子方法1
public void HookMethod1() {
}
//钩子方法2
public boolean HookMethod2() {
return true;
}
//抽象方法1
public abstract void abstractMethod1();
//抽象方法2
public abstract void abstractMethod2();
}
//含钩子方法的具体子类
class HookConcreteClass extends HookAbstractClass {
public void abstractMethod1() {
System.out.println("抽象方法1的实现被调用...");
}
public void abstractMethod2() {
System.out.println("抽象方法2的实现被调用...");
}
public void HookMethod1() {
System.out.println("钩子方法1被重写...");
}
public boolean HookMethod2() {
return false;
}
}
5、IOC源码实现
1、图片接口
2、具体办法源码
ConfigurableApplicationContext接口:
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
//该接口中定义的模板方法
void refresh() throws BeansException, IllegalStateException;
}
AbstractApplicationContext :
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean {
//实现ConfigurableApplicationContext接口中的模板方法
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
//模板方法中包含的基本方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
//钩子方法
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
/**
* 钩子方法
* Template method which can be overridden to add context-specific refresh work.
* Called on initialization of special beans, before instantiation of singletons.
* <p>This implementation is empty.
* @throws BeansException in case of errors
* @see #refresh()
*/
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
}
GenericApplicationContext:
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
//父类中基本方法的实现
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
/**
* Do nothing: We hold a single internal BeanFactory and rely on callers
* to register beans through our public methods (or the BeanFactory's).
* @see #registerBeanDefinition
*/
@Override
protected final void refreshBeanFactory() throws IllegalStateException {
if (!this.refreshed.compareAndSet(false, true)) {
throw new IllegalStateException(
"GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
}
this.beanFactory.setSerializationId(getId());
}
}
AbstractRefreshableApplicationContext:
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
//实现父类模板方法中的基本方法
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
synchronized (this.beanFactoryMonitor) {
if (this.beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - " +
"call 'refresh' before accessing beans via the ApplicationContext");
}
return this.beanFactory;
}
}
/**
* This implementation performs an actual refresh of this context's underlying
* bean factory, shutting down the previous bean factory (if any) and
* initializing a fresh bean factory for the next phase of the context's lifecycle.
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
}
6、模板方法注意
模板方法模式的注意事项和细节
- 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
- 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
- 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
- 一般模板方法都加上final关键字, 防止子类重写模板方法.
- 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模式来处理
8、观察者模式
1、观察者模式的概念
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
观察者模式是一种对象行为型模式,其主要优点如下。
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
- 目标与观察者之间建立了一套触发机制。
它的主要缺点如下。
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
在许多设计中,经常涉及多个对象都对一个特殊对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化,也就是说当对象间存在一对多关系时,在这样的情况下就可以使用观察者模式。当一个对象被修改时,则会自动通知它的依赖对象。
观察者模式是关于多个对象想知道一个对象中数据变化情况的一种成熟的模式。观察者模式中有一个称作“主题”的对象和若干个称作“观察者”的对象,“主题”和“观察者”间是一种一对多的依赖关系,当“主题”的状态发生变化时,所有“观察者”都得到通知。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
2、观察者模式的结构
观察者模式的结构中包含四种角色:
(1)主题(Subject):主题是一个接口,该接口规定了具体主题需要实现的方法,比如,添加、删除观察者以及通知观察者更新数据的方法。
(2)观察者(Observer):观察者是一个接口,该接口规定了具体观察者用来更新数据的方法。
(3)具体主题(ConcreteSubject):具体主题是实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题需使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体观察者。
(4)具体观察者(ConcreteObserver):具体观察者是实现观察者接口类的一个实例。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,使自己成为它的观察者,或让这个具体主题将自己从具体主题的集合中删除,使自己不再是它的观察者。
3、观察者模式的实现
观察者模式的实现代码如下:
package net.biancheng.c.observer;
import java.util.*;
public class ObserverPattern {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Observer obs1 = new ConcreteObserver1();
Observer obs2 = new ConcreteObserver2();
subject.add(obs1);
subject.add(obs2);
subject.notifyObserver();
}
}
//抽象目标
abstract class Subject {
protected List<Observer> observers = new ArrayList<Observer>();
//增加观察者方法
public void add(Observer observer) {
observers.add(observer);
}
//删除观察者方法
public void remove(Observer observer) {
observers.remove(observer);
}
public abstract void notifyObserver(); //通知观察者方法
}
//具体目标
class ConcreteSubject extends Subject {
public void notifyObserver() {
System.out.println("具体目标发生改变...");
System.out.println("--------------");
for (Object obs : observers) {
((Observer) obs).response();
}
}
}
//抽象观察者
interface Observer {
void response(); //反应
}
//具体观察者1
class ConcreteObserver1 implements Observer {
public void response() {
System.out.println("具体观察者1作出反应!");
}
}
//具体观察者1
class ConcreteObserver2 implements Observer {
public void response() {
System.out.println("具体观察者2作出反应!");
}
}
具体目标发生改变…
具体观察者1作出反应!
具体观察者2作出反应!
【例1】利用观察者模式设计一个程序,分析“人民币汇率”的升值或贬值对进口公司进口产品成本或出口公司的出口产品收入以及公司利润率的影响。
分析:当“人民币汇率”升值时,进口公司的进口产品成本降低且利润率提升,出口公司的出口产品收入降低且利润率降低;当“人民币汇率”贬值时,进口公司的进口产品成本提升且利润率降低,出口公司的出口产品收入提升且利润率提升。
这里的汇率(Rate)类是抽象目标类,它包含了保存观察者(Company)的 List 和增加/删除观察者的方法,以及有关汇率改变的抽象方法 change(int number);而人民币汇率(RMBrate)类是具体目标, 它实现了父类的 change(int number) 方法,即当人民币汇率发生改变时通过相关公司;公司(Company)类是抽象观察者,它定义了一个有关汇率反应的抽象方法 response(int number);进口公司(ImportCompany)类和出口公司(ExportCompany)类是具体观察者类,它们实现了父类的 response(int number) 方法,即当它们接收到汇率发生改变的通知时作为相应的反应。图 2 所示是其结构图。
程序代码如下:
package net.biancheng.c.observer;
import java.util.*;
public class RMBrateTest {
public static void main(String[] args) {
Rate rate = new RMBrate();
Company watcher1 = new ImportCompany();
Company watcher2 = new ExportCompany();
rate.add(watcher1);
rate.add(watcher2);
rate.change(10);
rate.change(-9);
}
}
//抽象目标:汇率
abstract class Rate {
protected List<Company> companys = new ArrayList<Company>();
//增加观察者方法
public void add(Company company) {
companys.add(company);
}
//删除观察者方法
public void remove(Company company) {
companys.remove(company);
}
public abstract void change(int number);
}
//具体目标:人民币汇率
class RMBrate extends Rate {
public void change(int number) {
for (Company obs : companys) {
((Company) obs).response(number);
}
}
}
//抽象观察者:公司
interface Company {
void response(int number);
}
//具体观察者1:进口公司
class ImportCompany implements Company {
public void response(int number) {
if (number > 0) {
System.out.println("人民币汇率升值" + number + "个基点,降低了进口产品成本,提升了进口公司利润率。");
} else if (number < 0) {
System.out.println("人民币汇率贬值" + (-number) + "个基点,提升了进口产品成本,降低了进口公司利润率。");
}
}
}
//具体观察者2:出口公司
class ExportCompany implements Company {
public void response(int number) {
if (number > 0) {
System.out.println("人民币汇率升值" + number + "个基点,降低了出口产品收入,降低了出口公司的销售利润率。");
} else if (number < 0) {
System.out.println("人民币汇率贬值" + (-number) + "个基点,提升了出口产品收入,提升了出口公司的销售利润率。");
}
}
}
人民币汇率升值10个基点,降低了进口产品成本,提升了进口公司利润率。
人民币汇率升值10个基点,降低了出口产品收入,降低了出口公司的销售利润率。
人民币汇率贬值9个基点,提升了进口产品成本,降低了进口公司利润率。
人民币汇率贬值9个基点,提升了出口产品收入,提升了出口公司的销售利润率。
观察者模式在软件幵发中用得最多的是窗体程序设计中的事件处理,窗体中的所有组件都是“事件源”,也就是目标对象,而事件处理程序类的对象是具体观察者对象。下面以一个学校铃声的事件处理程序为例,介绍 Windows 中的“事件处理模型”的工作原理。
【例2】利用观察者模式设计一个学校铃声的事件处理程序。
分析:在本实例中,学校的“铃”是事件源和目标,“老师”和“学生”是事件监听器和具体观察者,“铃声”是事件类。学生和老师来到学校的教学区,都会注意学校的铃,这叫事件绑定;当上课时间或下课时间到,会触发铃发声,这时会生成“铃声”事件;学生和老师听到铃声会开始上课或下课,这叫事件处理。这个实例非常适合用观察者模式实现,图 3 给出了学校铃声的事件模型。
现在用“观察者模式”来实现该事件处理模型。
首先,定义一个铃声事件(RingEvent)类,它记录了铃声的类型(上课铃声/下课铃声)。
再定义一个学校的铃(BellEventSource)类,它是事件源,是观察者目标类,该类里面包含了监听器容器 listener,可以绑定监听者(学生或老师),并且有产生铃声事件和通知所有监听者的方法。
然后,定义铃声事件监听者(BellEventListener)类,它是抽象观察者,它包含了铃声事件处理方法 heardBell(RingEvent e)。
最后,定义老师类(TeachEventListener)和学生类(StuEventListener),它们是事件监听器,是具体观察者,听到铃声会去上课或下课。图 4 给出了学校铃声事件处理程序的结构。
package net.biancheng.c.observer;
import java.util.*;
public class BellEventTest {
public static void main(String[] args) {
BellEventSource bell = new BellEventSource(); //铃(事件源)
bell.addPersonListener(new TeachEventListener()); //注册监听器(老师)
bell.addPersonListener(new StuEventListener()); //注册监听器(学生)
bell.ring(true); //打上课铃声
System.out.println("------------");
bell.ring(false); //打下课铃声
}
}
//铃声事件类:用于封装事件源及一些与事件相关的参数
class RingEvent extends EventObject {
private static final long serialVersionUID = 1L;
private boolean sound; //true表示上课铃声,false表示下课铃声
public RingEvent(Object source, boolean sound) {
super(source);
this.sound = sound;
}
public void setSound(boolean sound) {
this.sound = sound;
}
public boolean getSound() {
return this.sound;
}
}
//目标类:事件源,铃
class BellEventSource {
private List<BellEventListener> listener; //监听器容器
public BellEventSource() {
listener = new ArrayList<BellEventListener>();
}
//给事件源绑定监听器
public void addPersonListener(BellEventListener ren) {
listener.add(ren);
}
//事件触发器:敲钟,当铃声sound的值发生变化时,触发事件。
public void ring(boolean sound) {
String type = sound ? "上课铃" : "下课铃";
System.out.println(type + "响!");
RingEvent event = new RingEvent(this, sound);
notifies(event); //通知注册在该事件源上的所有监听器
}
//当事件发生时,通知绑定在该事件源上的所有监听器做出反应(调用事件处理方法)
protected void notifies(RingEvent e) {
BellEventListener ren = null;
Iterator<BellEventListener> iterator = listener.iterator();
while (iterator.hasNext()) {
ren = iterator.next();
ren.heardBell(e);
}
}
}
//抽象观察者类:铃声事件监听器
interface BellEventListener extends EventListener {
//事件处理方法,听到铃声
public void heardBell(RingEvent e);
}
//具体观察者类:老师事件监听器
class TeachEventListener implements BellEventListener {
public void heardBell(RingEvent e) {
if (e.getSound()) {
System.out.println("老师上课了...");
} else {
System.out.println("老师下课了...");
}
}
}
//具体观察者类:学生事件监听器
class StuEventListener implements BellEventListener {
public void heardBell(RingEvent e) {
if (e.getSound()) {
System.out.println("同学们,上课了...");
} else {
System.out.println("同学们,下课了...");
}
}
}
上课铃响!
老师上课了…
同学们,上课了.
下课铃响!
老师下课了…
同学们,下课了…
4、应用场景
在软件系统中,当系统一方行为依赖另一方行为的变动时,可使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。
通过前面的分析与应用实例可知观察者模式适合以下几种情形。
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
- 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
5、模式优缺点
观察者模式的效果有以下的优点:
- 观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。
- 观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知,
观察者模式有下面的缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。
- 如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
- 虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。
6、观察者模式在Spring中的应用
观察者模式在Spring中的主要体现在事件监听,事件机制的实现需要三个部分,事件源,事件,事件监听器;
1 ApplicationEvent抽象类作为事件的父类,通过source获取事件源。
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp = System.currentTimeMillis();
public ApplicationEvent(Object source) {
super(source);
}
public final long getTimestamp() {
return this.timestamp;
}
}
2 ApplicationListener接口作为事件监听器,继承自EventListener
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
3 ApplicationContext接口作为事件源,在这里可以将监听器注册进应用上下文中,也可以触发指定的事件。在ApplicationContext的父类ApplicationEventPublisher中有这样一个方法publishEvent,用以发布事件,具体的实现类在AbstractApplicationContext中。
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
void publishEvent(Object var1);
9、策略模式
在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法。
在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。
如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题。
1、策略的概述
策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要优点如下。
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
其主要缺点如下。
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类,增加维护难度。
2、策略模式的结构和实现
策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。
模式的结构
策略模式的主要角色如下。
- 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
其结构图如图 1 所示。
模式的实现
策略模式的实现代码如下:
public class StrategyPattern {
public static void main(String[] args) {
Context c = new Context();
Strategy s = new ConcreteStrategyA();
c.setStrategy(s);
c.strategyMethod();
System.out.println("-----------------");
s = new ConcreteStrategyB();
c.setStrategy(s);
c.strategyMethod();
}
}
//抽象策略类
interface Strategy {
public void strategyMethod(); //策略方法
}
//具体策略类A
class ConcreteStrategyA implements Strategy {
public void strategyMethod() {
System.out.println("具体策略A的策略方法被访问!");
}
}
//具体策略类B
class ConcreteStrategyB implements Strategy {
public void strategyMethod() {
System.out.println("具体策略B的策略方法被访问!");
}
}
//环境类
class Context {
private Strategy strategy;
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void strategyMethod() {
strategy.strategyMethod();
}
}
程序运行结果如下:
具体策略A的策略方法被访问!
具体策略B的策略方法被访问!
3、策略模式的应用实例
【例1】策略模式在“大闸蟹”做菜中的应用。
分析:关于大闸蟹的做法有很多种,我们以清蒸大闸蟹和红烧大闸蟹两种方法为例,介绍策略模式的应用。
首先,定义一个大闸蟹加工的抽象策略类(CrabCooking),里面包含了一个做菜的抽象方法 CookingMethod();然后,定义清蒸大闸蟹(SteamedCrabs)和红烧大闸蟹(BraisedCrabs)的具体策略类,它们实现了抽象策略类中的抽象方法;,所以将具体策略类定义成 JLabel 的子类;最后,定义一个厨房(Kitchen)环境类,它具有设置和选择做菜策略的方法;客户类通过厨房类获取做菜策略,并把做菜结果图在窗体中显示出来,图 2 所示是其结构图。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CrabCookingStrategy implements ItemListener {
private JFrame f;
private JRadioButton qz, hs;
private JPanel CenterJP, SouthJP;
private Kitchen cf; //厨房
private CrabCooking qzx, hsx; //大闸蟹加工者
CrabCookingStrategy() {
f = new JFrame("策略模式在大闸蟹做菜中的应用");
f.setBounds(100, 100, 500, 400);
f.setVisible(true);
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SouthJP = new JPanel();
CenterJP = new JPanel();
f.add("South", SouthJP);
f.add("Center", CenterJP);
qz = new JRadioButton("清蒸大闸蟹");
hs = new JRadioButton("红烧大闸蟹");
qz.addItemListener(this);
hs.addItemListener(this);
ButtonGroup group = new ButtonGroup();
group.add(qz);
group.add(hs);
SouthJP.add(qz);
SouthJP.add(hs);
//---------------------------------
cf = new Kitchen(); //厨房
qzx = new SteamedCrabs(); //清蒸大闸蟹类
hsx = new BraisedCrabs(); //红烧大闸蟹类
}
public void itemStateChanged(ItemEvent e) {
JRadioButton jc = (JRadioButton) e.getSource();
if (jc == qz) {
cf.setStrategy(qzx);
cf.CookingMethod(); //清蒸
} else if (jc == hs) {
cf.setStrategy(hsx);
cf.CookingMethod(); //红烧
}
CenterJP.removeAll();
CenterJP.repaint();
CenterJP.add((Component) cf.getStrategy());
f.setVisible(true);
}
public static void main(String[] args) {
new CrabCookingStrategy();
}
}
//抽象策略类:大闸蟹加工类
interface CrabCooking {
public void CookingMethod(); //做菜方法
}
//具体策略类:清蒸大闸蟹
class SteamedCrabs extends JLabel implements CrabCooking {
private static final long serialVersionUID = 1L;
public void CookingMethod() {
this.setIcon(new ImageIcon("src/strategy/SteamedCrabs.jpg"));
this.setHorizontalAlignment(CENTER);
}
}
//具体策略类:红烧大闸蟹
class BraisedCrabs extends JLabel implements CrabCooking {
private static final long serialVersionUID = 1L;
public void CookingMethod() {
this.setIcon(new ImageIcon("src/strategy/BraisedCrabs.jpg"));
this.setHorizontalAlignment(CENTER);
}
}
//环境类:厨房
class Kitchen {
private CrabCooking strategy; //抽象策略
public void setStrategy(CrabCooking strategy) {
this.strategy = strategy;
}
public CrabCooking getStrategy() {
return strategy;
}
public void CookingMethod() {
strategy.CookingMethod(); //做菜
}
}
4、策略模式的应用场景
策略模式在很多地方用到,如 Java SE 中的容器布局管理就是一个典型的实例,Java SE 中的每个容器都存在多种布局供用户选择。在程序设计中,通常在以下几种情况中使用策略模式较多。
- 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
5、源码实现
1、JDK中使用策略模式
在JDK中,我们调用数组工具类Arrays的一个排序方法sort时,可以使用默认的排序规则(升序),也可以自定义指定排序的规则,也就是可以自定义实现升序排序还是降序排序,方法的源码如下:
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);//若没有传入Comparator接口的实现类对象,也就是调用默认的排序方法
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
也就是说我们传递两个参数,一个是待排序的数组,另一个是Comparator接口的实现类对象,其中Comparator接口是一个函数式接口,接口里面定义了一个用于定义排序规则的抽象方法int compare(T o1, T o2);,由此可见,Comparator接口就是策略模式中的策略接口,它定义了一个排序算法,而具体的策略或者说具体的排序算法实现将由用户自定义实现。
1、不指定排序规则
//Arrays的sort方法默认是进行升序排序
public static void main(String[] args) {
int[] arr={10,11,9,-7,6,18,2};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));//[-7, 2, 6, 9, 10, 11, 18]
}
2、自定义
public static void main(String[] args) {
//使用匿名类的写法
Comparator<Integer> c=new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if(o1>o2){
return -1;
}else if(o1<o2){
return 1;
}else{
return 0;
}
}
};
Integer[] arr={10,11,9,-7,6,18,2};
Arrays.sort(arr, c);
System.out.println(Arrays.toString(arr));//[18, 11, 10, 9, 6, 2, -7]
}
2、Spring源码使用策略模式
DispatcherServlet在进行转发前需要进行传说中的九大件的初始化,其中去初始化时除了 initMultipartResolver(上传文件)没有获取 Properties defaultStrategies;默认策略,其他的八大件都会使用到策略模式。先看一下 defaultStrategies为 java.util.Properties类型,定义如下:
public class Properties extends Hashtable<Object, Object> {
// ***
}
流程梳理:
1、当Web容器启动时,ServletWebServerApplicationContext 初始化会调用其refresh()方法,则会调用 DispatcherServlet的onRefresh方法(Spring Ioc的流程可以参见:Spring Ioc和Mvc时序图)
2、onRefresh方法 - > initStrategies方法 -> 初始化九大件
3、初始化时则会调用getDefaultStrategy方法。如下:
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
List<T> strategies = this.getDefaultStrategies(context, strategyInterface);
if (strategies.size() != 1) {
throw new BeanInitializationException("DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
} else {
return strategies.get(0);
}
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value == null) {
return new LinkedList();
} else {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList(classNames.length);
String[] var7 = classNames;
int var8 = classNames.length;
for(int var9 = 0; var9 < var8; ++var9) {
String className = var7[var9];
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = this.createDefaultStrategy(context, clazz);
strategies.add(strategy);
} catch (ClassNotFoundException var13) {
throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", var13);
} catch (LinkageError var14) {
throw new BeanInitializationException("Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", var14);
}
}
return strategies;
}
}
5、那么Properties的值在哪里添加进去的呢,DispatcherServlet 的static静态代码块中会看见,是用Spring的Resource将配置文件中的配置加载,设置到这个Map容器中的,如下:
static {
try {
ClassPathResource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException var1) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + var1.getMessage());
}
}
6、在看看 DispatcherServlet.properties 配置就明白了,就是九大件没传时的默认值,其实也可以考虑用SPI机制实现(记得之前好像是不是有版本就是用SPI实现的)。
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
小结: web容器启动,ServletWebServerApplicationContext 的refresh方法 间接调用到 DispatcherServlet的初始九大件方法, 其中八大件在没有自定义实现的情况下,调用默认的 配置。 而默认配置则是在 DispatcherServlet的静态代码块中,由Spring的ClassPathResource将配置文件DispatcherServlet.properties中的配置加载进一个 Map容器中。只待初始化九大件时,根据不同的九大件类型作为key,调用相应的实现。
10、责任链模式
在现实生活中,一个事件需要经过多个对象处理是很常见的场景。例如,采购审批流程、请假流程等。公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据需要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这无疑增加了难度。
在计算机软硬件中也有相关例子,如总线网中数据报传送,每台计算机根据目标地址是否同自己的地址相同来决定是否接收;还有异常处理中,处理程序根据异常的类型决定自己是否处理该异常;还有 Struts2 的拦截器、JSP 和 Servlet 的 Filter 等,所有这些,都可以考虑使用责任链模式来实现。
1、模式的定义和特点
责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
注意:责任链模式也叫职责链模式。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。
责任链模式是一种对象行为型模式,其主要优点如下。
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
其主要缺点如下。
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
2、责任链的定义和实现
通常情况下,可以通过数据链表来实现职责链模式的数据结构。
模式的结构
职责链模式主要包含以下角色。
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。
其结构图如图 1 所示。客户端可按图 2 所示设置责任链。
模式的实现
职责链模式的实现代码如下:
package chainOfResponsibility;
public class ChainOfResponsibilityPattern {
public static void main(String[] args) {
//组装责任链
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
handler1.setNext(handler2);
//提交请求
handler1.handleRequest("two");
}
}
//抽象处理者角色
abstract class Handler {
private Handler next;
public void setNext(Handler next) {
this.next = next;
}
public Handler getNext() {
return next;
}
//处理请求的方法
public abstract void handleRequest(String request);
}
//具体处理者角色1
class ConcreteHandler1 extends Handler {
public void handleRequest(String request) {
if (request.equals("one")) {
System.out.println("具体处理者1负责处理该请求!");
} else {
if (getNext() != null) {
getNext().handleRequest(request);
} else {
System.out.println("没有人处理该请求!");
}
}
}
}
//具体处理者角色2
class ConcreteHandler2 extends Handler {
public void handleRequest(String request) {
if (request.equals("two")) {
System.out.println("具体处理者2负责处理该请求!");
} else {
if (getNext() != null) {
getNext().handleRequest(request);
} else {
System.out.println("没有人处理该请求!");
}
}
}
}
具体处理者2负责处理该请求!
在上面代码中,我们把消息硬编码为 String 类型,而在真实业务中,消息是具备多样性的,可以是 int、String 或者自定义类型。因此,在上面代码的基础上,可以对消息类型进行抽象 Request,增强了消息的兼容性。
3、模式的应用实例
【例1】用责任链模式设计一个请假条审批模块。
分析:假如规定学生请假小于或等于 2 天,班主任可以批准;小于或等于 7 天,系主任可以批准;小于或等于 10 天,院长可以批准;其他情况不予批准;这个实例适合使用职责链模式实现。
首先,定义一个领导类(Leader),它是抽象处理者,包含了一个指向下一位领导的指针 next 和一个处理假条的抽象处理方法 handleRequest(int LeaveDays);然后,定义班主任类(ClassAdviser)、系主任类(DepartmentHead)和院长类(Dean),它们是抽象处理者的子类,是具体处理者,必须根据自己的权力去实现父类的 handleRequest(int LeaveDays) 方法,如果无权处理就将假条交给下一位具体处理者,直到最后;客户类负责创建处理链,并将假条交给链头的具体处理者(班主任)。图 3 所示是其结构图。
package chainOfResponsibility;
public class LeaveApprovalTest {
public static void main(String[] args) {
//组装责任链
Leader teacher1 = new ClassAdviser();
Leader teacher2 = new DepartmentHead();
Leader teacher3 = new Dean();
//Leader teacher4=new DeanOfStudies();
teacher1.setNext(teacher2);
teacher2.setNext(teacher3);
//teacher3.setNext(teacher4);
//提交请求
teacher1.handleRequest(8);
}
}
//抽象处理者:领导类
abstract class Leader {
private Leader next;
public void setNext(Leader next) {
this.next = next;
}
public Leader getNext() {
return next;
}
//处理请求的方法
public abstract void handleRequest(int LeaveDays);
}
//具体处理者1:班主任类
class ClassAdviser extends Leader {
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 2) {
System.out.println("班主任批准您请假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
//具体处理者2:系主任类
class DepartmentHead extends Leader {
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 7) {
System.out.println("系主任批准您请假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
//具体处理者3:院长类
class Dean extends Leader {
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 10) {
System.out.println("院长批准您请假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
//具体处理者4:教务处长类
class DeanOfStudies extends Leader {
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 20) {
System.out.println("教务处长批准您请假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
程序运行结果如下:
院长批准您请假8天。
假如增加一个教务处长类,可以批准学生请假 20 天,也非常简单,代码如下:
//具体处理者4:教务处长类
class DeanOfStudies extends Leader {
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 20) {
System.out.println("教务处长批准您请假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
4、应用场景
前边已经讲述了关于责任链模式的结构与特点,下面介绍其应用场景,责任链模式通常在以下几种情况使用。
- 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
- 可动态指定一组对象处理请求,或添加新的处理者。
- 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
5、源码的应用
HandlerExecutionChain
其中我们可以看到,在springMVC中,DispatcherServlet这个核心类中使用到了HandlerExecutionChain这个类,他就是责任链模式实行的具体类。在DispatcherServlet的doDispatch这个方法中,我们可以看到它贯穿了整个请求dispatch的流程:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 获取该请求的handler,每个handler实为HandlerExecutionChain,它为一个处理链,负责处理整个请求
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 责任链执行预处理方法,实则是将请求交给注册的请求拦截器执行
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 实际的执行逻辑的部分,也就是你加了@RequestMapping注解的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 责任链执行后处理方法,实则是将请求交给注册的请求拦截器执行
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理返回的结果,触发责任链上注册的拦截器的AfterCompletion方法,其中也用到了HandlerExecutionChain注册的handler来处理错误结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 触发责任链上注册的拦截器的AfterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}