设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
1995年,GoF(Gang of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了23种设计模式,从此树立了软件设计模式领域的里程碑,人称【GoF设计模式】。
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
正确使用设计模式有以下优点:
GoF 23
创建型模式(5种):描述怎样去创建一个对象
结构型模式(7种):描述如何将我们的类或者对象按照某种布局组成一些更大的结构
行为性模式(11种):描述类或者对象之间怎能够相互协作,去共同完成单个对象无法完成的任务,主要是分配一些职责
单例模式(Singleton Pattern)是一个比较简单的模式,其定义如下:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
角色分析:Singleton类称为单例类,通过使用private的构造函数确保了在一个应用中只产生一个实例,并且是自行实例化的(在Singleton中自己使用new Singleton())

单例模式的通用源代码如下所示:
package com.kmu.shu.singleton;
public class Singleton {
private static final Singleton singleton = new Singleton();
//限制产生多个对象
private Singleton(){
}
//通过该方法获得实例对象
public static Singleton getSingleton(){
return singleton;
}
//类中其他方法,尽量是static
public static void doSomething(){
}
}
(1)自从秦始皇确立了皇帝这个位置以后,同一时期基本上就只有一个人坐在这个位置

只有两个类,Emperor代表皇帝类,Minister代表臣子类,关联到皇帝类非常简单。
(2)皇帝类实现
package com.kmu.shu.singleton;
//皇帝类
public class Emperor {
private static final Emperor emperor =new Emperor(); //初始化一个皇帝
private Emperor(){
//世俗和道德约束,目的就是不希望产生第二个皇帝
}
public static Emperor getInstance(){
return emperor;
}
//皇帝发话了
public void say(){
System.out.println("朕是唯一的一个皇帝....");
}
}
(3)臣子类实现
package com.kmu.shu.singleton;
//臣子类
public class Minister {
public static void main(String[] args) {
for(int day=0;day<3;day++){
Emperor emperor = Emperor.getInstance();
emperor.say();
}
}
}
(4)运行结果

优点
(1) 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
(2) 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制)。
(3)单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
(4)单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
缺点
(1)单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽 象类是不可能被实例化的。当然,在特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。
(2)单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
(3) 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。
使用场景
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体的场景如下:
(1)要求生成唯一序列号的环境;
(2)在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
(3)创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
(4)需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
单例模式是23个模式中比较简单的模式,应用也非常广泛:
如在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理这些Bean的生命期,决定什么时候创建出来,什么时候销毁,销毁的时候要如何处理,等等。
如果采用非单例模式(Prototype类型),则Bean初始化后的管理交由J2EE容器,Spring容器不再跟踪管理Bean的生命周期
作用:
OOP七大原则:
开闭原则:一个软件的实体应当对扩展开放,对修改关闭
依赖倒置原则:要针对接口编程,不要针对实现编程
迪米特法则:只与你的直接朋友通信,不要和陌生人说话
核心本质:
三种模式:
传统方法:
车接口
package com.kmu.shu.factory.simple;
public interface Car {
void name();
}
车类
package com.kmu.shu.factory.simple;
public class Tesla implements Car{
@Override
public void name() {
System.out.println("特斯拉");
}
}
public class WuLing implements Car{
@Override
public void name() {
System.out.println("五菱宏光");
}
}
消费者
package com.kmu.shu.factory.simple;
public class Consumer {
public static void main(String[] args) {
//1.传统方式:接口,所有的实现类!拿车需要new车!
Car wuLing = new WuLing();
Car tesla = new Tesla();
wuLing.name();
tesla.name();
}
}
简单工厂模式:
创建一个车工厂
package com.kmu.shu.factory.simple;
//静态工厂模式(简单工厂模式)
//弊端:增加一个新的产品,如果你不修改代码,做不到!
//车工厂:直接拿车就可以了
//开闭原则:不改变原来的类!使用简单工厂需要改变
public class CarFactory {
//方法一:判断是什么车返回什么车
public static Car getCar(String car){
if(car.equals("五菱")){
return new WuLing();
}else if(car.equals("特斯拉")){
return new Tesla();
}else {
return null;
}
}
//方法二:想要什么返回什么
public static Car getWuLing(){
return new WuLing();
}
public static Car getTesla(){
return new Tesla();
}
}
消费者
package com.kmu.shu.factory.simple;
public class Consumer {
public static void main(String[] args) {
//1.传统方式:接口,所有的实现类!
/*Car wuLing = new WuLing();
Car tesla = new Tesla();
wuLing.name();
tesla.name();*/
//2.使用工厂创建:Consumer,不需要new车,直接通过工厂去拿车
Car wuLing = CarFactory.getCar("五菱");
Car tesla = CarFactory.getCar("特斯拉");
wuLing.name();
tesla.name();
}
}
现在需要增加一个大众
public class DaZhong implements Car{
@Override
public void name() {
System.out.println("大众");
}
}
这时候就需要在原有的代码上修改车工厂类去生产一辆大众车(违反了开闭原则!)
package com.kmu.shu.factory.simple;
//静态工厂模式(简单工厂模式)
//弊端:增加一个新的产品,如果你不修改代码,做不到!
//车工厂:直接拿车就可以了
//开闭原则:不改变原来的类!使用简单工厂需要改变
public class CarFactory {
//方法一:判断是什么车返回什么车
public static Car getCar(String car){
if(car.equals("五菱")){
return new WuLing();
}else if(car.equals("特斯拉")){
return new Tesla();
}else if(car.equals("大众")){
return new DaZhong();
}else {
return null;
}
}
//方法二:想要什么返回什么
public static Car getWuLing(){
return new WuLing();
}
public static Car getTesla(){
return new Tesla();
}
public static Car getDaZhong(){
return new DaZhong();
}
}
画图分析

车接口
package com.kmu.shu.factory.method;
//车接口
public interface Car {
void name();
}
车工厂接口
package com.kmu.shu.factory.method;
//工厂方法模式:
//车工厂接口
public interface CarFactory {
Car getCar();
}
特斯拉车
package com.kmu.shu.factory.method;
public class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
特斯拉车工厂
package com.kmu.shu.factory.method;
public class TeslaFactory implements CarFactory{
@Override
public Car getCar() {
return new Tesla();
}
}
五菱宏光
package com.kmu.shu.factory.method;
public class WuLing implements Car {
@Override
public void name() {
System.out.println("五菱宏光");
}
}
五菱宏光车工厂
package com.kmu.shu.factory.method;
public class WuLingFactory implements CarFactory {
@Override
public Car getCar() {
return new WuLing();
}
}
消费者
package com.kmu.shu.factory.method;
public class Consumer {
public static void main(String[] args) {
Car wuLing = new WuLingFactory().getCar();
Car tesla = new TeslaFactory().getCar();
wuLing.name();
tesla.name();
}
}
增加一个大众车
package com.kmu.shu.factory.method;
public class DaZhong implements Car{
@Override
public void name() {
System.out.println("大众");
}
}
大众车工厂
package com.kmu.shu.factory.method;
public class DaZhongFactory implements CarFactory{
@Override
public Car getCar() {
return new DaZhong();
}
}
消费者
package com.kmu.shu.factory.method;
public class Consumer {
public static void main(String[] args) {
Car wuLing = new WuLingFactory().getCar();
Car tesla = new TeslaFactory().getCar();
wuLing.name();
tesla.name();
//增加一个大众车
Car daZhong = new DaZhongFactory().getCar();
daZhong.name();
}
}
画图分析(虽然满足了开闭原则,可以横向扩展,但是很明显发现类变多了!)

根据设计原则:工厂方法模式!满足了开闭原则
根据实际业务:简单工厂模式!
**定义:**抽象工厂模式提供了一个创建一系列相关或者相互依赖的接口,无需指定它们具体的类
适用场景:
优点:
缺点:
抽象工厂模式:
UML类图

产品族理解

IPhoneProduct接口
package com.kmu.shu.abstractFactory;
//手机产品接口
public interface IPhoneProduct {
//开机
void start();
//关机
void shutdown();
//打电话
void callup();
//发短信
void sendSMS();
}
HuaweiPhone
package com.kmu.shu.abstractFactory;
//华为手机
public class HuaweiPhone implements IPhoneProduct {
@Override
public void start() {
System.out.println("开启华为手机");
}
@Override
public void shutdown() {
System.out.println("关闭华为手机");
}
@Override
public void callup() {
System.out.println("华为手机打电话");
}
@Override
public void sendSMS() {
System.out.println("华为手机发短信");
}
}
XiaomiPhone
package com.kmu.shu.abstractFactory;
//小米手机
public class XiaomiPhone implements IPhoneProduct {
@Override
public void start() {
System.out.println("开启小米手机");
}
@Override
public void shutdown() {
System.out.println("关闭小米手机");
}
@Override
public void callup() {
System.out.println("小米手机打电话");
}
@Override
public void sendSMS() {
System.out.println("小米手机发短信");
}
}
IRouterProduct
package com.kmu.shu.abstractFactory;
//路由器产品接口
public interface IRouterProduct {
//开机
void start();
//关机
void shutdown();
//打开WiFi
void openWiFi();
//设置参数
void setting();
}
HuaweiRouter
package com.kmu.shu.abstractFactory;
//华为路由器
public class HuaweiRouter implements IRouterProduct{
@Override
public void start() {
System.out.println("开启华为路由器");
}
@Override
public void shutdown() {
System.out.println("关闭华为路由器");
}
@Override
public void openWiFi() {
System.out.println("打开华为WiFi");
}
@Override
public void setting() {
System.out.println("设置华为路由器");
}
}
XiaomiRouter
package com.kmu.shu.abstractFactory;
//小米路由器
public class XiaomiRouter implements IRouterProduct{
@Override
public void start() {
System.out.println("开启小米路由器");
}
@Override
public void shutdown() {
System.out.println("关闭小米路由器");
}
@Override
public void openWiFi() {
System.out.println("打开小米WiFi");
}
@Override
public void setting() {
System.out.println("设置小米路由器");
}
}
IProductFactory
package com.kmu.shu.abstractFactory;
//抽象产品工厂:抽象产品工厂生产抽象产品
public interface IProductFactory {
//生产手机
IPhoneProduct iPhoneProduct();
//生产路由器
IRouterProduct iRouterProduct();
}
XiaomiFactory
package com.kmu.shu.abstractFactory;
//小米工厂
public class XiaomiFactory implements IProductFactory{
@Override
public IPhoneProduct iPhoneProduct() {
//直接生产一个小米手机:new XiaomiPhone()
return new XiaomiPhone();
}
@Override
public IRouterProduct iRouterProduct() {
//直接生产一个小米路由器:new XiaomiRouter()
return new XiaomiRouter();
}
}
HuaweiFactory
package com.kmu.shu.abstractFactory;
public class HuaweiFactory implements IProductFactory{
@Override
public IPhoneProduct iPhoneProduct() {
//直接生产一个华为手机
return new HuaweiPhone();
}
@Override
public IRouterProduct iRouterProduct() {
//直接生产一个华为路由器
return new HuaweiRouter();
}
}
Client
package com.kmu.shu.abstractFactory;
public class Client {
public static void main(String[] args) {
System.out.println("==============小米系列产品=================");
//小米工厂
XiaomiFactory xiaomiFactory = new XiaomiFactory();
//小米工厂生产一个小米手机
IPhoneProduct xiaomiPhone = xiaomiFactory.iPhoneProduct();
//启动小米手机
xiaomiPhone.start();
xiaomiPhone.callup();
//小米工厂生产一个小米路由器
IRouterProduct xiaomiRouter = xiaomiFactory.iRouterProduct();
xiaomiRouter.start();
xiaomiRouter.openWiFi();
System.out.println("==============华为系列产品=================");
//华为工厂
HuaweiFactory huaweiFactory = new HuaweiFactory();
//华为工厂生产华为手机
IPhoneProduct huaweiPhone = huaweiFactory.iPhoneProduct();
huaweiPhone.start();
huaweiPhone.callup();
//华为工厂生产一个华为路由器
IRouterProduct huaweiRouter = huaweiFactory.iRouterProduct();
huaweiRouter.start();
huaweiRouter.openWiFi();
}
}
类图分析

若需生产笔记本产品,则需要修改代码,不满足开闭原则!不可以增加产品,可以增加产品族!
package com.kmu.shu.abstractFactory;
//抽象产品工厂:抽象产品工厂生产抽象产品
public interface IProductFactory {
//生产手机
IPhoneProduct iPhoneProduct();
//生产路由器
IRouterProduct iRouterProduct();
//若需生产笔记本产品,则需要修改代码,不满足开闭原则
//ILaptopProduct iLaptopProduct();
}
简单工厂模式(静态工厂模式)
工厂方法模式
抽象工厂模式
应用场景:
建造者模式也属于创建型模式,它提供了一种创建对象的最佳方式。
**定义:**将一个复杂对象的构建与它得到表示分离,使得同样的构建过程可以创建不同的表示(例如造房子,一样的过程可以建造平房、高楼、四合院等等)
主要作用:
在用户不知道对象的建造和细节的情况下就可以直接创建复杂的对象
在用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
例子:
角色分析

既然是建造者模式,那么我们还是继续造房子吧,其实我也想不到更简单的例子。假设造房简化为如下步骤:
如果要盖一座房子,首先要找一个建筑公司或者工程承包商(指挥者)。承包商通过图纸(房屋结构)指挥工人(具体建造者)过来造房子(产品),最后验收。
Builder
package com.kmu.shu.builder;
//抽象的建造者:不负责建房子,只负责定义一些方法和接口
public abstract class Builder {
abstract void buildA(); //地基
abstract void buildB(); //钢筋工程
abstract void buildC(); //铺电线
abstract void buildD(); //粉刷
//完工:得到产品
abstract Product getProduct();
}
Product
package com.kmu.shu.builder;
//产品:房子
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) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
}
Worker
package com.kmu.shu.builder;
//具体的建造者:工人,继承抽象类Builder
public class Worker extends Builder{
private Product product;
//工人创建一个产品
public Worker() {
product = new Product();
}
@Override
void buildA() {
product.setBuildA("地基");
System.out.println("地基");
}
@Override
void buildB() {
product.setBuildB("钢筋工程");
System.out.println("钢筋工程");
}
@Override
void buildC() {
product.setBuildC("铺电线");
System.out.println("铺电线");
}
@Override
void buildD() {
product.setBuildD("粉刷");
System.out.println("粉刷");
}
@Override
Product getProduct() {
return product;
}
}
Director
package com.kmu.shu.builder;
//指挥:核心。负责指挥构建一个工程,工程如何构建,由它决定
public class Director {
//指挥,指挥工人按照顺序建房子
public Product build(Builder builder){
builder.buildA();
builder.buildB();
builder.buildC();
builder.buildD();
return builder.getProduct();
}
}
Test
package com.kmu.shu.builder;
public class Test {
public static void main(String[] args) {
//指挥
Director director = new Director();
//指挥具体的工人完成产品
Product build = director.build(new Worker());
build.toString();
}
}
小结
Builder
package com.kmu.shu.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();
}
Product
package com.kmu.shu.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) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
}
Worker
package com.kmu.shu.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.setBuildA(msg);
//将当前对象返回给它
return this;
}
@Override
Builder buildC(String msg) {
product.setBuildB(msg);
//将当前对象返回给它
return this;
}
@Override
Builder buildD(String msg) {
product.setBuildC(msg);
//将当前对象返回给它
return this;
}
@Override
Product getProduct() {
return product;
}
}
Test
package com.kmu.shu.builder.demo02;
public class Test {
public static void main(String[] args) {
//服务员
Worker worker = new Worker();
//服务员创建自己的产品:默认不做选择
Product product = worker.getProduct();
System.out.println(product.toString());
}
}
用户自定义套餐
package com.kmu.shu.builder.demo02;
public class Test {
public static void main(String[] args) {
/*//服务员
Worker worker = new Worker();
//服务员创建自己的产品:默认不做选择
Product product = worker.getProduct();
System.out.println(product.toString());*/
//服务员
Worker worker = new Worker();
//链式编程:在原来的基础上可以自由组合了,如果不组合也有默认的套餐
Product product = worker.buildA("全家桶").buildB("雪碧")
.getProduct();
System.out.println(product.toString());
}
}
优点
(1)产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
(2)具体的建造者类之间是相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库的代码,符合“开闭原则“。
缺点
(1)建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
(2)如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
应用场景
(1)需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
(2)隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。适合于一个具有较多的零件(属性)的产品(对象)的创建过程。
建造者与抽象工厂模式的比较
(1)与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族
(2)在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。
(3)如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车!
原型模式
(1)浅克隆:
Video
package com.kmu.shu.prototype.demo01;
import java.util.Date;
/*
* 1、实现一个接口 Cloneable
* 2、重写一个方法 clone()
* */
//Video:视频类
public class Video implements Cloneable{//无良up主,克隆别人的视频!
private String name;
private Date creatTime;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreatTime() {
return creatTime;
}
public void setCreatTime(Date creatTime) {
this.creatTime = creatTime;
}
public Video(String name, Date creatTime) {
this.name = name;
this.creatTime = creatTime;
}
public Video() {
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", creatTime=" + creatTime +
'}';
}
}
Bilibili
package com.kmu.shu.prototype.demo01;
import java.util.Date;
//客户端:克隆
public class Bilibili {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象:v1
Date date = new Date();
Video v1 = new Video("小舒的Vlog",date);
System.out.println("v1=>"+v1);
System.out.println("v1=>hash:"+v1.hashCode());
//v1 克隆 v2
//Video v2 = new Video("小舒的Vlog",date); //原来的操作
//Object clone = v1.clone();
Video v2 = (Video)v1.clone(); //将Object对象强制转换为Video对象
System.out.println("v2=>"+v2);
System.out.println("v2=>hash:"+v2.hashCode());
v2.setName("Clone:小舒的Vlog");
System.out.println("v2=>"+v2);
/*
输出:hashCode不一样说明是两个对象
v1=>Video{name='小舒的Vlog', creatTime=Fri Oct 07 15:30:27 CST 2022}
v1=>hash:1735600054
v2=>Video{name='小舒的Vlog', creatTime=Fri Oct 07 15:30:27 CST 2022}
v2=>hash:21685669
v2=>Video{name='Clone:小舒的Vlog', creatTime=Fri Oct 07 15:30:27 CST 2022}*/
}
}
浅克隆,用的是同一个date对象,若改变v1的属性,v2也会跟着变
package com.kmu.shu.prototype.demo02;
import java.util.Date;
//客户端:克隆
public class Bilibili {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象:v1
Date date = new Date();
Video v1 = new Video("小舒的Vlog",date);
Video v2 = (Video)v1.clone(); //将Object对象强制转换为Video对象
System.out.println("v1=>"+v1);
System.out.println("v2=>"+v2);
System.out.println("============================");
date.setTime(2050);
System.out.println("v1=>"+v1);
System.out.println("v2=>"+v2);
/*v1=>Video{name='小舒的Vlog', creatTime=Fri Oct 07 15:38:47 CST 2022}
v2=>Video{name='小舒的Vlog', creatTime=Fri Oct 07 15:38:47 CST 2022}
============================
v1=>Video{name='小舒的Vlog', creatTime=Thu Jan 01 08:00:02 CST 1970}
v2=>Video{name='小舒的Vlog', creatTime=Thu Jan 01 08:00:02 CST 1970}*/
}
}

(2)深克隆:改变v1的属性,v2不会改变
Video
package com.kmu.shu.prototype.demo02;
import java.util.Date;
//Video:视频类
public class Video implements Cloneable{//无良up主,克隆别人的视频!
private String name;
private Date creatTime;
@Override
//深克隆:需要改变clone()方法,除了这个就是序列化与反序列化
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
Video v = (Video) obj;
//将这个对象v的属性也进行克隆
//this:表示当前类
//this.creatTime表示当前时间对象
v.creatTime = (Date) this.creatTime.clone();
return obj;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreatTime() {
return creatTime;
}
public void setCreatTime(Date creatTime) {
this.creatTime = creatTime;
}
public Video(String name, Date creatTime) {
this.name = name;
this.creatTime = creatTime;
}
public Video() {
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", creatTime=" + creatTime +
'}';
}
}
Bilibili
package com.kmu.shu.prototype.demo02;
import java.util.Date;
//Spring Bean的创建:
// 1、单例模式创建
// 2、原型模式创建:通过拷贝来实现,不用重复的创造,从而节省了一些空间
public class Bilibili {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象:v1
Date date = new Date();
Video v1 = new Video("小舒的Vlog",date);
Video v2 = (Video)v1.clone(); //将Object对象强制转换为Video对象
System.out.println("v1=>"+v1);
System.out.println("v2=>"+v2);
System.out.println("============================");
date.setTime(2050);
System.out.println("v1=>"+v1);
System.out.println("v2=>"+v2);
}
}
代理模式的分类:

角色分析:
代码步骤:
接口
//租房
public interface Rent {
public void rent();
}
真实角色
//房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
代理角色
//中介
public class Proxy implements Rent{
private Host host; //使用组合方式:多用组合,少用继承
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
contract();
fare();
host.rent(); //帮房东租房子
}
//看房
public void seeHouse(){
System.out.println("中介带你看房");
}
//签合同
public void contract(){
System.out.println("签租赁合同");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
客户端访问代理角色
//客户
public class Client {
public static void main(String[] args) {
//房东要租房子
Host host = new Host();
//代理,中介帮房东租房子,但是呢?代理角色一般会有一些附属操作
Proxy proxy = new Proxy(host);
//你不用面对房东,直接找中介租房即可!
proxy.rent();
}
}
代理模式的好处:
缺点:一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率会变低
代码步骤:
接口
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
真实角色
//真实对象
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
代理角色
/*
改动原有的业务代码,在公司中是大忌!
*/
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
//spring注入一个对象建议使用set方法
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
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 userServiceProxy = new UserServiceProxy();
userServiceProxy.setUserService(userService);
userServiceProxy.add();
}
}
需要了解两个类:Proxy:代理 InvocationHandler:调用处理程序
代码步骤:
接口
//租房
public interface Rent {
public void rent();
}
真实角色
//房东
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子了");
}
}
代理调用处理程序
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//等会我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成得到代理类直接通过newProxyInstance:这段代码是死的!只需要修改被代理的接口 rent.getClass().getInterfaces()参数
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用代理类之前执行seeHouse()方法
seeHouse();
//动态代理的本质就是使用反射机制实现
Object result = method.invoke(rent, args);
//调用代理类之后执行fare()方法
fare();
return result;
}
public void seeHouse(){
System.out.println("中介带看房子");
}
public void fare(){
System.out.println("收中介费");
}
}
客户端访问
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理角色:现在没有,需要使用ProxyInvocationHandler类生成
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象!
pih.setRent(host);
//动态生成代理类proxy,我们并没有写它
Rent proxy = (Rent) pih.getProxy();
//使用代理类调用方法
proxy.rent();
}
}
动态代理的好处:
代码步骤:
生成模板类(直接套用即可)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//等会我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类直接通过newProxyInstance:这段代码是死的!只需要修改被代理的接口 rent.getClass().getInterfaces()参数
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
@Override
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 msg){
System.out.println("执行了"+msg+"方法");
}
}
客户访问类(只需要修改此类即可实现)
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理角色,不存在
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
//动态生成代理类
UserService proxy = (UserService) pih.getProxy();
proxy.delete();
}
}
**定义:**桥接模式(bridge)是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interfce)模式。

**分析:**这个场景中有两个变化的维度:品牌,类型;多继承结构耦合性太强了,而且不符合单一职责原则

实现:
Brand
package com.kmu.shu.bridge;
//品牌
public interface Brand {
void info();
}
Lenovo
package com.kmu.shu.bridge;
//联想品牌
public class Lenovo implements Brand{
@Override
public void info() {
System.out.print("联想");
}
}
Apple
package com.kmu.shu.bridge;
public class Apple implements Brand{
@Override
public void info() {
System.out.print("苹果");
}
}
Computer:桥的作用
package com.kmu.shu.bridge;
//抽象的电脑类型类 桥
public abstract class Computer {
//组合,电脑出厂自带品牌
protected Brand brand; //私有的只有本类可以访问,protected,当子类继承父类的时候便可以访问
public Computer(Brand brand) {
this.brand = brand;
}
public void info(){
//自带品牌
brand.info();
}
}
class Desktop extends Computer{
//有这个构造方法,调用的时候就可以调用父类的构造方法
public Desktop(Brand brand) {
super(brand);
}
@Override
public void info(){
super.info();
System.out.println("台式机");
}
}
class Laptop extends Computer{
//有这个构造方法,调用的时候就可以调用父类的构造方法
public Laptop(Brand brand) {
super(brand);
}
@Override
public void info(){
super.info();
System.out.println("笔记本");
}
}
Test
package com.kmu.shu.bridge;
public class Test {
public static void main(String[] args) {
//苹果笔记本
Computer appleLaptop = new Laptop(new Apple());
appleLaptop.info();
//联想台式机
Computer lenovoDesktop = new Desktop(new Lenovo());
lenovoDesktop.info();
}
}
画图分析:避免了多继承,可以随意扩展

优点
(1)桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本
(2)桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则,就像一座桥,可以把两个变化的维度连接起来!
缺点
(1)桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
最佳实践
(1)如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
(2)一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
(3)虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
应用场景
(1)Java语言通过Java虚拟机实现了平台的无关性
(2)AWT中的Peer架构
(3)JDBC驱动程序也是桥接模式的应用之一
我们先来看一个快餐店的例子。
快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。

使用继承的方式存在的问题:
扩展性不好
如果要再加一种配料(火腿肠),我们就会发现需要给FriedRice和FriedNoodles分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类。
产生过多的子类
定义:
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
结构
装饰(Decorator)模式中的角色:
我们使用装饰者模式对快餐店案例进行改进,体会装饰者模式的精髓。
类图如下:
代码如下:
//快餐接口
public abstract class FastFood {
private float price;
private String desc;
public FastFood() {
}
public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}
public void setPrice(float price) {
this.price = price;
}
public float getPrice() {
return price;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public abstract float cost(); //获取价格
}
//炒饭
public class FriedRice extends FastFood {
public FriedRice() {
super(10, "炒饭");
}
public float cost() {
return getPrice();
}
}
//炒面
public class FriedNoodles extends FastFood {
public FriedNoodles() {
super(12, "炒面");
}
public float cost() {
return getPrice();
}
}
//配料类
public abstract class Garnish extends FastFood {
private FastFood fastFood;
public FastFood getFastFood() {
return fastFood;
}
public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}
public Garnish(FastFood fastFood, float price, String desc) {
super(price,desc);
this.fastFood = fastFood;
}
}
//鸡蛋配料
public class Egg extends Garnish {
public Egg(FastFood fastFood) {
super(fastFood,1,"鸡蛋");
}
public float cost() {
return getPrice() + getFastFood().getPrice();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
//培根配料
public class Bacon extends Garnish {
public Bacon(FastFood fastFood) {
super(fastFood,2,"培根");
}
@Override
public float cost() {
return getPrice() + getFastFood().getPrice();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
//测试类
public class Client {
public static void main(String[] args) {
//点一份炒饭
FastFood food = new FriedRice();
//花费的价格
System.out.println(food.getDesc() + " " + food.cost() + "元");
System.out.println("========");
//点一份加鸡蛋的炒饭
FastFood food1 = new FriedRice();
food1 = new Egg(food1);
//花费的价格
System.out.println(food1.getDesc() + " " + food1.cost() + "元");
System.out.println("========");
//点一份加培根的炒面
FastFood food2 = new FriedNoodles();
food2 = new Bacon(food2);
//花费的价格
System.out.println(food2.getDesc() + " " + food2.cost() + "元");
}
}
好处:
饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况主要有两类:
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
当对象的功能要求可以动态地添加,也可以再动态地撤销时。
IO流中的包装类使用到了装饰者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。
我们以BufferedWriter举例来说明,先看看如何使用BufferedWriter
public class Demo {
public static void main(String[] args) throws Exception{
//创建BufferedWriter对象
//创建FileWriter对象
FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
BufferedWriter bw = new BufferedWriter(fw);
//写数据
bw.write("hello Buffered");
bw.close();
}
}
使用起来感觉确实像是装饰者模式,接下来看它们的结构:
小结:
BufferedWriter使用装饰者模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。
静态代理和装饰者模式的区别:
如果去欧洲国家去旅游的话,他们的插座如下图最左边,是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑,手机在当地不能直接充电。所以就需要一个插座转换器,转换器第1面插入当地的插座,第2面供我们充电,这样使得我们的插头在当地能使用。生活中这样的例子很多,手机充电器(将220v转换为5v的电压),读卡器等,其实就是使用到了适配器模式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h4SOI4Jj-1668053334547)(https://kevin-1311972042.cos.ap-chengdu.myqcloud.com/img/%E8%BD%AC%E6%8E%A5%E5%A4%B4.png)]
定义:
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
适配器模式(Adapter)包含以下主要角色:
实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
【例】读卡器
现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。
类图如下:
代码如下:
//SD卡的接口
public interface SDCard {
//读取SD卡方法
String readSD();
//写入SD卡功能
void writeSD(String msg);
}
//SD卡实现类
public class SDCardImpl implements SDCard {
public String readSD() {
String msg = "sd card read a msg :hello word SD";
return msg;
}
public void writeSD(String msg) {
System.out.println("sd card write msg : " + msg);
}
}
//电脑类
public class Computer {
public String readSD(SDCard sdCard) {
if(sdCard == null) {
throw new NullPointerException("sd card null");
}
return sdCard.readSD();
}
}
//TF卡接口
public interface TFCard {
//读取TF卡方法
String readTF();
//写入TF卡功能
void writeTF(String msg);
}
//TF卡实现类
public class TFCardImpl implements TFCard {
public String readTF() {
String msg ="tf card read msg : hello word tf card";
return msg;
}
public void writeTF(String msg) {
System.out.println("tf card write a msg : " + msg);
}
}
//定义适配器类(SD兼容TF)
public class SDAdapterTF extends TFCardImpl implements SDCard {
public String readSD() {
System.out.println("adapter read tf card ");
return readTF();
}
public void writeSD(String msg) {
System.out.println("adapter write tf card");
writeTF(msg);
}
}
//测试类
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
System.out.println(computer.readSD(sdCard));
System.out.println("------------");
SDAdapterTF adapter = new SDAdapterTF();
System.out.println(computer.readSD(adapter));
}
}
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。
实现方式:对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
【例】读卡器
我们使用对象适配器模式将读卡器的案例进行改写。类图如下:
代码如下:
类适配器模式的代码,我们只需要修改适配器类(SDAdapterTF)和测试类。
//创建适配器对象(SD兼容TF)
public class SDAdapterTF implements SDCard {
private TFCard tfCard;
public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}
public String readSD() {
System.out.println("adapter read tf card ");
return tfCard.readTF();
}
public void writeSD(String msg) {
System.out.println("adapter write tf card");
tfCard.writeTF(msg);
}
}
//测试类
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
System.out.println(computer.readSD(sdCard));
System.out.println("------------");
TFCard tfCard = new TFCardImpl();
SDAdapterTF adapter = new SDAdapterTF(tfCard);
System.out.println(computer.readSD(adapter));
}
}
注意:还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可。
Reader(字符流)、InputStream(字节流)的适配使用的是InputStreamReader。
InputStreamReader继承自java.io包中的Reader,对他中的抽象的未实现的方法给出实现。如:
public int read() throws IOException {
return sd.read();
}
public int read(char cbuf[], int offset, int length) throws IOException {
return sd.read(cbuf, offset, length);
}
如上代码中的sd(StreamDecoder类对象),在Sun的JDK实现中,实际的方法实现是对sun.nio.cs.StreamDecoder类的同名方法的调用封装。类结构图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9vkQNLD1-1668053334547)(https://kevin-1311972042.cos.ap-chengdu.myqcloud.com/img/%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F-jdk%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.png)]
从上图可以看出:
结论:
从表层来看,InputStreamReader做了InputStream字节流类到Reader字符流之间的转换。而从如上Sun JDK中的实现类关系结构中可以看出,是StreamDecoder的设计实现在实际上采用了适配器模式。
有些人可能炒过股票,但其实大部分人都不太懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,刚开始炒股肯定都会想,如果有个懂行的帮帮手就好,其实基金就是个好帮手,支付宝里就有许多的基金,它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外汇等领域,而基金投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。
定义:
又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观(Facade)模式是“迪米特法则”的典型应用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HEodusNL-1668053334547)(https://kevin-1311972042.cos.ap-chengdu.myqcloud.com/img/%E5%A4%96%E8%A7%82%E6%A8%A1%E5%BC%8F%E5%BC%95%E5%85%A5.jpg)]
外观(Facade)模式包含以下主要角色:
【例】智能家电控制
小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。类图如下:
代码如下:
//灯类
public class Light {
public void on() {
System.out.println("打开了灯....");
}
public void off() {
System.out.println("关闭了灯....");
}
}
//电视类
public class TV {
public void on() {
System.out.println("打开了电视....");
}
public void off() {
System.out.println("关闭了电视....");
}
}
//控制类
public class AirCondition {
public void on() {
System.out.println("打开了空调....");
}
public void off() {
System.out.println("关闭了空调....");
}
}
//智能音箱
public class SmartAppliancesFacade {
private Light light;
private TV tv;
private AirCondition airCondition;
public SmartAppliancesFacade() {
light = new Light();
tv = new TV();
airCondition = new AirCondition();
}
public void say(String message) {
if(message.contains("打开")) {
on();
} else if(message.contains("关闭")) {
off();
} else {
System.out.println("我还听不懂你说的!!!");
}
}
//起床后一键开电器
private void on() {
System.out.println("起床了");
light.on();
tv.on();
airCondition.on();
}
//睡觉一键关电器
private void off() {
System.out.println("睡觉了");
light.off();
tv.off();
airCondition.off();
}
}
//测试类
public class Client {
public static void main(String[] args) {
//创建外观对象
SmartAppliancesFacade facade = new SmartAppliancesFacade();
//客户端直接与外观对象进行交互
facade.say("打开家电");
facade.say("关闭家电");
}
}
好处:
缺点:
使用tomcat作为web容器时,接收浏览器发送过来的请求,tomcat会将请求信息封装成ServletRequest对象,如下图①处对象。但是大家想想ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们知道该request对象肯定是一个HttpServletRequest对象的子实现类对象,到底是哪个类的对象呢?可以通过输出request对象,我们就会发现是一个名为RequestFacade的类的对象。
RequestFacade类就使用了外观模式。先看结构图:
为什么在此处使用外观模式呢?
定义 RequestFacade 类,分别实现 ServletRequest ,同时定义私有成员变量 Request ,并且方法的实现调用 Request 的实现。然后,将 RequestFacade上转为 ServletRequest 传给 servlet 的 service 方法,这样即使在 servlet 中被下转为 RequestFacade ,也不能访问私有成员变量对象中的方法。既用了 Request ,又能防止其中方法被不合理的访问。
对于这个图片肯定会非常熟悉,上图我们可以看做是一个文件系统,对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可以对叶子节点进行相关的操作。可以将这颗树理解成一个大的容器,容器里面包含很多的成员对象,这些成员对象即可是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象和叶子对象,但是这样就会给客户带来不必要的麻烦,作为客户而已,它始终希望能够一致的对待容器对象和叶子对象。
定义:
又名部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
组合模式主要包含三种角色:
【例】软件菜单
如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。
要实现该案例,我们先画出类图:
代码实现:
不管是菜单还是菜单项,都应该继承自统一的接口,这里姑且将这个统一的接口称为菜单组件。
//菜单组件:抽象根节点
public abstract class MenuComponent {
//菜单组件的名称
protected String name;
protected int level;
//添加子菜单功能
public void add(MenuComponent menuComponent){
throw new UnsupportedOperationException();//菜单项不能使用该方法
}
//移除子菜单功能
public void remove(MenuComponent menuComponent){
throw new UnsupportedOperationException();//菜单项不能使用该方法
}
//获取指定的子菜单,需要传一个索引
public MenuComponent getChild(int index){
throw new UnsupportedOperationException();
}
//获取菜单或者菜单项的名称
public String getName(){
return name;
}
//打印菜单名称的方法(包含子菜单和菜单项)
public abstract void print();
}
这里的MenuComponent定义为抽象类,因为有一些共有的属性和行为要在该类中实现,Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法,举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常,你也可以根据自己的需要改写默认实现。
import java.util.ArrayList;
import java.util.List;
//菜单类:树枝节点角色
public class Menu extends MenuComponent{
//菜单可以有多个子菜单或者菜单项
private List<MenuComponent> menuComponentList = new ArrayList<MenuComponent>();
//构造方法
public Menu(String name,int level) {
this.name = name;
this.level = level;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}
@Override
public MenuComponent getChild(int index) {
return menuComponentList.get(index);
}
@Override
public void print() {
//打印菜单名称
for (int i=0;i<level;i++){
System.out.print("--");
}
System.out.println(name);
//打印子菜单或者子菜单项的名称
for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}
Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能。
//菜单项类:叶子结点角色
public class MenuItem extends MenuComponent{
public MenuItem(String name,int level){
this.name = name;
this.level = level;
}
@Override
public void print() {
//打印菜单项的名称
for (int i=0;i<level;i++){
System.out.print("--");
}
System.out.println(name);
}
}
MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现。
Client类
package com.kmu.shu.combination;
public class Cilent {
public static void main(String[] args) {
//创建菜单树
MenuComponent menu1 = new Menu("菜单管理",2);
menu1.add(new MenuItem("页面访问",3));
menu1.add(new MenuItem("展开菜单",3));
menu1.add(new MenuItem("编辑菜单",3));
menu1.add(new MenuItem("新增菜单",3));
menu1.add(new MenuItem("删除菜单",3));
MenuComponent menu2 = new Menu("权限管理",2);
menu2.add(new MenuItem("页面访问",3));
menu2.add(new MenuItem("提交保存",3));
MenuComponent menu3 = new Menu("角色管理",2);
menu3.add(new MenuItem("页面访问",3));
menu3.add(new MenuItem("新增角色",3));
menu3.add(new MenuItem("修改角色",3));
//创建一级菜单
MenuComponent component = new Menu("系统管理",1);
//将二级菜单添加到一级菜单中
component.add(menu1);
component.add(menu2);
component.add(menu2);
//打印菜单名称(如果有子菜单,一起打印出来)
component.print();
}
}
在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式。
透明组合模式
透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent 声明了 add、remove 、getChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)
安全组合模式
在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。
组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多级目录呈现等树形结构数据的操作。
定义:
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。
享元(Flyweight )模式中存在以下两种状态:
享元模式的主要有以下角色:
【例】俄罗斯方块
下面的图片是众所周知的俄罗斯方块中的一个个方块,如果在俄罗斯方块这个游戏中,每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,下面利用享元模式进行实现。
先来看类图:
代码如下:
俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。
public abstract class AbstractBox {
public abstract String getShape();
public void display(String color) {
System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);
}
}
接下来就是定义不同的形状了,IBox类、LBox类、OBox类等。
public class IBox extends AbstractBox {
@Override
public String getShape() {
return "I";
}
}
public class LBox extends AbstractBox {
@Override
public String getShape() {
return "L";
}
}
public class OBox extends AbstractBox {
@Override
public String getShape() {
return "O";
}
}
提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。
public class BoxFactory {
private static HashMap<String, AbstractBox> map;
private BoxFactory() {
map = new HashMap<String, AbstractBox>();
AbstractBox iBox = new IBox();
AbstractBox lBox = new LBox();
AbstractBox oBox = new OBox();
map.put("I", iBox);
map.put("L", lBox);
map.put("O", oBox);
}
public static final BoxFactory getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final BoxFactory INSTANCE = new BoxFactory();
}
public AbstractBox getBox(String key) {
return map.get(key);
}
}
1,优点
2,缺点:
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
3,使用场景:
Integer类使用了享元模式。我们先看下面的例子:
public class Demo {
public static void main(String[] args) {
Integer i1 = 127;
Integer i2 = 127;
System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2));
Integer i3 = 128;
Integer i4 = 128;
System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4));
}
}
运行上面代码,结果如下:
为什么第一个输出语句输出的是true,第二个输出语句输出的是false?通过反编译软件进行反编译,代码如下:
public class Demo {
public static void main(String[] args) {
Integer i1 = Integer.valueOf((int)127);
Integer i2 Integer.valueOf((int)127);
System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());
Integer i3 = Integer.valueOf((int)128);
Integer i4 = Integer.valueOf((int)128);
System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());
}
}
上面代码可以看到,直接给Integer类型的变量赋值基本数据类型数据的操作底层使用的是 valueOf() ,所以只需要看该方法即可
public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
}
可以看到 Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象。