1面向对象原则
单一职责原则:
即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。 当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 A1,A2
接口隔离原则:
客户端不应该依赖它不需要的接 口,即一个类对另一个类的依赖 应该建立在最小的接口上
依赖倒转原则:
面向接口编程
里氏替换原则:
继承必须确保超类所拥有的性质在子类中仍然成立,在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法 ,在适当的情况下,可 以通过聚合,组合,依赖 来解决问题。
开闭原则:
扩展开放,修改关闭
迪米特法则:
最少知道原则,即一个类对自己依赖的类知道的 越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内 部。对外除了提供的public 方法,不对外泄露任何信息
迪米特法则还有个更简单的定义:只与直接的朋友通信
合成复用原则:
原则是尽量使用合成/聚合的方式,而不是使用继承
1 单例模式
单例模式分为饿汉式和懒汉式,饿汉式是当类加载的时候就生成实例,懒汉式是只有需要生成实例的时候才会生成实例。
1.1饿汉式
package com.single;
//饿汉式,有可能会浪费内存
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=new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
1.2懒汉式
public class LazyMan{
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
if(lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}
}
单线程下,这个代码有效,当运行环境为多线程的时候,会出错。
1.3双重检测锁DCL
//双重检测锁模式 懒汉式单例 DCL
public static LazyMan getInstance(){//volatile避免指令重拍
if (lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
双重 检查锁定背后的理论是:在 //2 处的第二次检查使(如清单 3 中那样)创建两个不同的 Singleton 对象成为不可能。假设有下列事件序列:
线程 1 进入 getInstance() 方法。
由于 lazyMan 为 null,线程 1 在 //1 处进入 synchronized 块。
线程 1 被线程 2 预占。
线程 2 进入 getInstance() 方法。
由于 instance 仍旧为 null,线程 2 试图获取 //1 处的锁。然而,由于线程 1 持有该锁,线程 2 在 //1 处阻塞。
线程 2 被线程 1 预占。
线程 1 执行,由于在 //2 处实例仍旧为 null,线程 1 还创建一个 Singleton 对象并将其引用赋值给 instance。
线程 1 退出 synchronized 块并从 getInstance() 方法返回实例。
线程 1 被线程 2 预占。
线程 2 获取 //1 处的锁并检查 instance 是否为 null。
由于 instance 是非 null 的,并没有创建第二个 Singleton 对象,由线程 1 创建的对象被返回。
双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。
双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。
Java 无序写入:分析这行代码lazyMan=new LazyMan();,这行代码不是原子操作。这行代码的背后操作:
*1分配内存空间
* 2 执行构造方法,初始化对象
* 3 把这个对象指向这个空间
有可能执行成132,此时lazyMan是空,所以需要避免指令重排。
使用volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。
private volatile static LazyMan lazyMan;
/*public static LazyMan getInstance(){
if(lazyMan==null)
lazyMan=new LazyMan();
return lazyMan;
}*/
//双重检测锁模式 懒汉式单例 DCL
public static LazyMan getInstance(){//volatile避免指令重拍
if (lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
/*
* 不是原子操作
*1分配内存空间
* 2 执行构造方法,初始化对象
* 3 把这个对象只想这个空间
* 123
* 132A
* B由于已经指向了这个空间,会认为这个对象不为空
*
* */
}
}
}
return lazyMan;
}
1.4使用内部类实现单例模式
package com.single;
//静态内部类实现
//不安全
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
使用静态内部类能保证线程安全的原因
由于内部静态类只会被加载一次,故该实现方式是线程安全的
类加载的初始化阶段是单线程的
类加载时机
使用new,invokestatic,putstatic,getstatic指令时若该类未加载则触发
反射使用某个类时若该类未加载则触发
子类加载时若父类未加载则触发
程序开始时主方法所在的类会被加载
…
静态内部类的懒加载应该是第一种情况。为什么外部类加载时静态内部类未加载,《effective java》里面说静态内部类只是刚好写在了另一个类里面,实际上和外部类没什么附属关系。(但直接放在外部,1. 如果设置为public访问没有限制 2. private的话访问受限)
线程安全是因为,类加载的初始化阶段是单线程的,类变量的赋值语句在编译生成字节码的时候写在函数中,初始化时单线程调用这个完成类变量的赋值。
静态内部类不会随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的
因为是在内部类加载和初始化时创建的,因此是线程安全的
因为jdk在加载类时是线程安全的
1.5通过反射破坏单例模式
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyMan instance=LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor=LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);//在Java中可以通过反射进行获取实体类中的字段值,当未设置Field的setAccessible方法为true时,会在调用的时候进行访问安全检查,会抛出IllegalAccessException异常
//那么,解决方案就是设置Field对象的Accessible的访问标志位为Ture,就可以通过反射获取私有变量的值,在访问时会忽略访问修饰符的检查.
//设置为true 可以使用私有化的构造器
LazyMan instace2=declaredConstructor.newInstance();
}
通过发射绕过构造器私有化
结决方法 在构造器中加上判断
private LazyMan(){
synchronized (LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("不要破坏单例模式");
}
}
//System.out.println(Thread.currentThread().getName());
}
不通过getInstance的 new出来的对象指针不会交给laztMan
private volatile static LazyMan lazyMan;
因为是直接从反射中获取的对象
两个对象都从反射中获取:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
/*for(int i=0;i<10;i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}*/
Constructor<LazyMan> declaredConstructor=LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);//在Java中可以通过反射进行获取实体类中的字段值,当未设置Field的setAccessible方法为true时,会在调用的时候进行访问安全检查,会抛出IllegalAccessException异常
//那么,解决方案就是设置Field对象的Accessible的访问标志位为Ture,就可以通过反射获取私有变量的值,在访问时会忽略访问修饰符的检查.
//
LazyMan instance=declaredConstructor.newInstance();
LazyMan instace2=declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instace2);
}
解决方法,增加一个判别位。
private static boolean flag=false;
private LazyMan(){
synchronized (LazyMan.class){
if(flag==false)
flag=true;
else {
throw new RuntimeException("不要破坏单例模式");
}
}
//System.out.println(Thread.currentThread().getName());
}
解决方法,通过反射拿到判别位,改变判别位。
所有都不安全。
1.6使用枚举
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("doSomething");
}
}
调用方法:
public class Main {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}
直接通过Singleton.INSTANCE.doSomething()的方式调用即可。方便、简洁又安全。
1.7枚举类不能被反射
package com.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSingle {
INSTACNE;
public EnumSingle getInstacne(){
return INSTACNE;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance=EnumSingle.INSTACNE;
Constructor<EnumSingle> declareConstructor=EnumSingle.class.getDeclaredConstructor();
declareConstructor.setAccessible(true);
EnumSingle instace2=declareConstructor.newInstance();
System.out.println(instance);
System.out.println(instace2);
}
}
运行结果:
此结果是编译没有成功,我们反编译这个类,查看构造方法:
枚举最终反编译代码
之后通过jad反编译
jad -sjava
后面加上class文件路径
经过jad反编译之后的代码
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
package com.single;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstacne()
{
return INSTACNE;
}
public static void main(String args[])
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException
{
EnumSingle instance = INSTACNE;
Constructor declareConstructor = com/single/EnumSingle.getDeclaredConstructor(new Class[0]);
declareConstructor.setAccessible(true);
EnumSingle instace2 = (EnumSingle)declareConstructor.newInstance(new Object[0]);
System.out.println(instance);
System.out.println(instace2);
}
private static EnumSingle[] $values()
{
return (new EnumSingle[] {
INSTACNE
});
}
public static final EnumSingle INSTACNE = new EnumSingle("INSTACNE", 0);
private static final EnumSingle $VALUES[] = $values();
}
可以看到构造方法需要传入Stirng 和int作为参数
package com.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSingle {
INSTACNE;
public EnumSingle getInstacne(){
return INSTACNE;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance=EnumSingle.INSTACNE;
Constructor<EnumSingle> declareConstructor=EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declareConstructor.setAccessible(true);
EnumSingle instace2=declareConstructor.newInstance();
System.out.println(instance);
System.out.println(instace2);
}
}
即使我们调用了正确的构造方法,枚举同样不能被反射。
通过看反射的源码,可以看出反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。
2工厂模式
2.1 核心思想
实现了创建者和调用者分离。
详细分类:简单工厂模式,工厂方法模式,抽象工厂模式
核心本质:实质化对象不使用new 用工厂方法代替。
将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
三种模式:
简单工厂模式:用来生产同一等级结构中的任意产品。
工厂方法模式:用来生产同一等级结构中的固定产品。
抽象工厂模式:围绕一个超级工厂创建其他工厂。
2.2简单工厂模式
以买车为例
package com.kuang.factory.simple;
public interface Car {
public void name();
}
创建Car实体类
package com.kuang.factory.simple;
public class WuLing implements Car{
@Override
public void name() {
System.out.println("五菱宏光");
}
}
package com.kuang.factory.simple;
public class Tesla implements Car{
@Override
public void name() {
System.out.println("特斯拉,刹不住");
}
}
传统买车方法:
package com.kuang.factory.simple;
public class Consumer {
public static void main(String[] args) {
Car car=new WuLing();
Car car1=new Tesla();
car.name();
car1.name();
}
}
必须手动new对象。
简单工厂模式:
package com.kuang.factory.simple;
//静态工厂,不方便动态增加新的车,比如我们想新增大众车,必须修改代码
public class CarFactory {
public static Car getCar(String car){
if(car.equals("WuLing")){
return new WuLing();
}else if (car.equals("Tesla")){
return new Tesla();
}
return null;
}
}
package com.kuang.factory.simple;
public class Consumer {
public static void main(String[] args) {
Car car2=CarFactory.getCar("WuLing");
car2.name();
}
}
静态工厂,不方便动态增加新的车,比如我们想新增大众车,必须修改代码,违反了OOP中的开闭原则。
如果需要增加大众的,必须修改车工厂中的代码。
2.3工厂方法模式
新增一个工厂的抽象类:
package com.kuang.factory.method;
public interface Factory {
public Car getCar();
}
建造相对应的工厂:
package com.kuang.factory.method;
public class WuLingFactory implements Factory{
@Override
public Car getCar() {
return new WuLing();
}
}
package com.kuang.factory.method;
public class TeslaFactory implements Factory{
@Override
public Car getCar() {
return new Tesla();
}
}
实现类:
package com.kuang.factory.method;
import com.kuang.factory.simple.CarFactory;
public class Consumer {
public static void main(String[] args) {
Factory wulingFactory=new WuLingFactory();
Factory teslaFactory=new TeslaFactory();
Car car=wulingFactory.getCar();
Car car1=teslaFactory.getCar();
car1.name();
car.name();
}
}
小结:
简单工厂(静态):虽然某种程度上不符合设计原则,但实际使用最多。
工厂方法模式:不修改已有类前提下,通过增加新的工厂类实现拓展。
抽象工厂模式:不可以增加产品,可以增加产品族。
2.4抽象工厂模式
上面两种模式不管工厂怎么拆分抽象,都只是针对一类产品Car(AbstractProduct),如果要生成另一种产品PC,应该怎么表示呢?
最简单的方式是把2中介绍的工厂方法模式完全复制一份,不过这次生产的是PC。但同时也就意味着我们要完全复制和修改Phone生产管理的所有代码,显然这是一个笨办法,并不利于扩展和维护。
抽象工厂模式通过在AbstarctFactory中增加创建产品的接口,并在具体子工厂中实现新加产品的创建,当然前提是子工厂支持生产该产品。否则继承的这个接口可以什么也不干。工厂的工厂
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
代码如下:
PC接口
package com.kuang.factory.abstract1;
public interface PC {
void start();
void close();
void internet();
void email();
}
Phone接口
package com.kuang.factory.abstract1;
public interface Phone {
void start();
void close();
void call();
void song();
}
工厂接口
package com.kuang.factory.abstract1;
public interface abstractFactory {
PC pcProduct();
Phone phoneProduct();
}
小米工厂
package com.kuang.factory.abstract1;
public class XiaoMiFactory implements abstractFactory{
@Override
public PC pcProduct() {
return new XiaoMiPC();
}
@Override
public Phone phoneProduct() {
return new XiaoMiPhone();
}
}
华为工厂
package com.kuang.factory.abstract1;
public class HuaWeiFactory implements abstractFactory{
@Override
public PC pcProduct() {
return new HuaWeiPC();
}
@Override
public Phone phoneProduct() {
return new HuaWeiPhone();
}
}
相关产品
package com.kuang.factory.abstract1;
public class HuaWeiPC implements PC{
@Override
public void start() {
System.out.println("华为PC开机");
}
@Override
public void close() {
System.out.println("华为PC关机");
}
@Override
public void internet() {
System.out.println("华为PC上网");
}
@Override
public void email() {
System.out.println("华为PC发邮件");
}
}
测试代码
package com.kuang.factory.abstract1;
public class Test {
public static void main(String[] args) {
abstractFactory xiaoMiFactory=new XiaoMiFactory();
PC xiaomiPC=xiaoMiFactory.pcProduct();
Phone xiaomiPhone=xiaoMiFactory.phoneProduct();
xiaomiPC.close();
xiaomiPC.email();
xiaomiPC.internet();
xiaomiPhone.call();
xiaomiPhone.close();
xiaomiPhone.song();
xiaomiPhone.start();
abstractFactory huaWeiFactory=new HuaWeiFactory();
PC huaweiPC=huaWeiFactory.pcProduct();
Phone huaweiPhone=huaWeiFactory.phoneProduct();
huaweiPC.internet();
huaweiPC.email();
huaweiPC.start();
huaweiPhone.start();
huaweiPhone.song();
huaweiPhone.call();
}
}
抽象工厂模式很好的发挥了开闭原则、依赖倒置原则,但缺点是抽象工厂模式太重了,如果 IFactory 接口需要新增功能,则会影响到所有的具体工厂类。使用抽象工厂模式,替换具体工厂时只需更改一行代码,但要新增抽象方法则需要修改所有的具体工厂类。所以抽象工厂模式适用于增加同类工厂这样的横向扩展需求,不适合新增功能这样的纵向扩展。
3 建造者模式
建造者模式也属于创建型模式,它提供了一种创建对象的最佳方式。
定义:
将一个复杂对象的构建与它对的表示进行分离,使得同样的构建过程可以创建不同的表示
主要作用
在用户不知道对象的建造过程和细节的情况下就可以创建复杂的对象。(说白了就是把内部的建造过程和细节隐藏起来)
例如:
工厂(建造者模式):负责制造汽车(组装过程和细节在工厂内)
汽车购买者(用户):你只需要说出你需要的型号(就是对象的类型和内容),然后付钱直接购买即可使用(不需要知道汽车是怎么组装的)
实用范围
1、当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
2、当构造过程必须允许被构造的对象有不同表示时。
角色
在这样的设计模式中,有以下几个角色:
1、Builder:为创建一个产品对象的各个部件指定抽象接口。
2、ConcreteBuilder:实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。
3、Director:构造一个使用Builder接口的对象,指导构建过程,也用来隔离用户与建造过程的关联
4、Product:表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,包括将这些部件装配成最终产品的接口。
对于建造房子来说,建造的过程是固定的,区别在于房屋的细节,例如一室一厅,两室一厅,房屋具体的大小是不同的。
什么时候使用建造者模式?
主要是用于创建一些复杂的对象,这些对象内部构建间的建造顺序通常是稳定的,但对象内部的构建常常面临着复杂的变化。
建造者模式和工厂模式的区别:工厂模式不关心构建的流程,只关心什么产品由什么工厂生产即可。
建造者模式会关心流程,注重的是对零件的装配,组合,封装。
Builder类,
package com.kuang.Builder;
public interface Builder {
void buildlivingroom();
void buildbedroom();
void buildkitchen();
Product getProduct();
}
Builder的实现类:
package com.kuang.Builder;
public class BigHouse implements Builder{
private Product product=new Product();
@Override
public void buildlivingroom() {
product.setLivingroom("大客厅");
System.out.println("建造大客厅");
}
@Override
public void buildbedroom() {
product.setBedroom("大卧室");
System.out.println("建造大卧室");
}
@Override
public void buildkitchen() {
product.setKitchen("大厨房");
System.out.println("建造大厨房");
}
@Override
public Product getProduct() {
return product;
}
}
package com.kuang.Builder;
public class SmallHouse implements Builder{
private Product product=new Product();
@Override
public void buildlivingroom() {
product.setLivingroom("小客厅");
System.out.println("建造小客厅");
}
@Override
public void buildbedroom() {
product.setBedroom("小卧室");
System.out.println("建造小卧室");
}
@Override
public void buildkitchen() {
product.setKitchen("小厨房");
System.out.println("建造小厨房");
}
@Override
public Product getProduct() {
return product;
}
}
Director类,用来指挥建造过程
package com.kuang.Builder;
public class Director {
public Product build(Builder builder){
builder.buildlivingroom();
builder.buildbedroom();
builder.buildkitchen();
return builder.getProduct();
}
}
上面是Builder模式的常规写法,导演类Director在Builder模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合
通过静态内部类方式实现零件无序装配构建,这种方式使用更加灵活,更符合定义。内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产出不同复杂产品
比如:麦当劳的套餐,服务员(具体构建者)可以随意搭配任意几种产品(零件) 组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要是因为第二种方式把指挥者交给用户来操作,使得产品的创建更加简单灵活。
//建造者
public abstract class Builder {
abstract Builder builderA(String msg); //汉堡
abstract Builder builderB(String msg); //可乐
abstract Builder builderC(String msg); //薯条
abstract Builder builderD(String msg);//甜点
abstract Product getProduct();
}
package builder.demo2;
//产品:套餐
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) {
BuilderA = builderA;
}
public String getBuilderB() {
return BuilderB;
}
public void setBuilderB(String builderB) {
BuilderB = builderB;
}
public String getBuilderC() {
return BuilderC;
}
public void setBuilderC(String builderC) {
BuilderC = builderC;
}
public String getBuilderD() {
return BuilderD;
}
public void setBuilderD(String builderD) {
BuilderD = builderD;
}
@Override
public String toString() {
return "Product{" +
"BuilderA='" + BuilderA + '\'' +
", BuilderB='" + BuilderB + '\'' +
", BuilderC='" + BuilderC + '\'' +
", BuilderD='" + BuilderD + '\'' +
'}';
}
}
package builder.demo2;
//具体的构建者
public class Worker extends Builder {
private Product product;
public Worker( ) {
product = new Product();
}
@Override
Builder builderA(String msg) {
product.setBuilderA(msg);
return this;
}
@Override
Builder builderB(String msg) {
product.setBuilderB(msg);
return this;
}
@Override
Builder builderC(String msg) {
product.setBuilderC(msg);
return this;
}
@Override
Builder builderD(String msg) {
product.setBuilderD(msg);
return this;
}
@Override
Product getProduct() {
return product;
}
}
package builder.demo2;
import javax.jws.WebService;
public class Test {
public static void main(String[] args) {
//服务员
Worker worker=new Worker();
//链式编程
Product product=worker.builderA("鸡腿").getProduct();
System.out.println(product.toString());
}
}
优点
产品的构建和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节。
将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
具体的构建者类之间是相互独立的,这有利于系统的扩展。增加新的具体构建者无需修改原有类库的代码,符合开闭原则。
缺点
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
如果产品的内部变化复杂,可能会导致需要定义很多具体的构造者来实现这种变化,导致系统变得很庞大。
应用场景
需要生产的产品对象有复杂的内部结构,这些产品对象具备共性;
隔离复杂对象的创建和使用,并使得使用的创建过程可以创建不同的产品。
适合于一个具有较多的零件(属性)的产品(对象)的创建过程。
建造者和抽象工厂模式的比较:
与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列的相关产品,这些产品位于不同的产品等级结构,构成了一个产品族。
在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥类来指导如何生成对象,包括对象的组装过程和建造步骤,他侧重于一步步构造一个复杂对象,返回一个完整的对象。
如果将抽象工厂模式堪称汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
4原型设计模式
原型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式其实就是从一个对象在创建另外一个可定制的对象,而且不需要知道任何的创建细节。
为什么需要原型模式?
如果我们需要大量的相同对象,如果每次都通过new生成这些对象,那么会十分的消耗时间。每NEW一次,都需要执行一次构造函数,如果构造函数的执行时间很长,那么多次执行初始化操作会十分的低效。一般在初始化信息不发生变化的情况下,克隆是最好的方法,隐藏了对象创建的细节,又对性能是大大的提高。
JAVA中实现原型模式只需要实现 Cloneable接口,重写clone方法。
下面我举例说明:
假如我是一名应届毕业生,要给公司投递大量的简历,如果简历一个一个的生成,显然是不方便的。我们可以使用原型模式解决这个问题。
package com.kuang.Prototype;
import java.util.Date;
public class Resume implements Cloneable{
private String name;
private String sex;
private String age;
private WorkExperience work;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public void SetWorkExperience(Date date,String company){
work.workDate=date;
work.company=company;
}
public Resume(String name, String sex, String age, WorkExperience work) {
this.name = name;
this.sex = sex;
this.age = age;
this.work = work;
}
public Resume() {
work=new WorkExperience();
}
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age='" + age + '\'' +
", work=" + work +
'}';
}
public void Display(){
System.out.println(name+" "+sex+" "+age);
System.out.println(work);
}
}
package com.kuang.Prototype;
import java.util.Date;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Resume a=new Resume();
a.setName("大鸟");
a.setAge("18");
a.setSex("男");
a.SetWorkExperience(new Date(1221),"xx-公司");
Resume b=(Resume) a.clone();
b.SetWorkExperience(new Date(222222),"YY企业");
Resume c=(Resume) a.clone();
c.SetWorkExperience(new Date(),"ZZ企业");
a.Display();
b.Display();
c.Display();
}
}
这样就快速生成了三份简历。
如果我们在简历类中增加一个工作经历的类,其中含有时间和公司名称等属性,简历类直接调用这个对象。
package com.kuang.Prototype;
import java.util.Date;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Resume a=new Resume();
a.setName("大鸟");
a.setAge("18");
a.setSex("男");
a.SetWorkExperience(new Date(1221),"xx-公司");
Resume b=(Resume) a.clone();
b.SetWorkExperience(new Date(222222),"YY企业");
Resume c=(Resume) a.clone();
c.SetWorkExperience(new Date(),"ZZ企业");
a.Display();
b.Display();
c.Display();
}
}
运行结果
这样的结果并不是我们想要的,这就与原型模式中的浅复制和深复制有关了。
浅复制:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
其中三次赋值,相当于对同一个对象进行三次赋值。
深复制把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
我们将工作经历类实现 Cloneable接口,重写clone方法。
package com.kuang.Prototype;
import java.util.Date;
public class WorkExperience implements Cloneable{
public Date workDate;
public String company;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public WorkExperience() {
}
public WorkExperience(Date workDate, String company) {
this.workDate = workDate;
this.company = company;
}
@Override
public String toString() {
return "WorkExperience{" +
"workDate=" + workDate +
", company='" + company + '\'' +
'}';
}
public Date getWorkDate() {
return workDate;
}
public void setWorkDate(Date workDate) {
this.workDate = workDate;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
}
在简历类中修改clone方法 ,让work变成可以clone的类。
package com.kuang.Prototype;
import java.util.Date;
public class Resume implements Cloneable{
private String name;
private String sex;
private String age;
private WorkExperience work;
@Override
protected Object clone() throws CloneNotSupportedException {
Object o=super.clone();
Resume resume=(Resume) o;
resume.work= (WorkExperience) this.work.clone();
return resume;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public void SetWorkExperience(Date date,String company){
work.workDate=date;
work.company=company;
}
public Resume(String name, String sex, String age, WorkExperience work) {
this.name = name;
this.sex = sex;
this.age = age;
this.work = work;
}
public Resume() {
work=new WorkExperience();
}
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age='" + age + '\'' +
", work=" + work +
'}';
}
public void Display(){
System.out.println(name+" "+sex+" "+age);
System.out.println(work);
}
}
这样就实现了深复制。
在一些特定的场合,会经常涉及到深复制或浅复制,比如数据集对象DataSet它就有Clone和copy方法,clone方法用来复制数据集结构,但不复制数据,相当于浅复制,开销比较小。copy方法全部复制,实现类深复制。
5适配器模式
适配器模式:将一个类的接口转换成客户希望的另一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器又分为两种:一种是类适配器和对象适配器。
下面我们以一个笔记本上网的例子说明,现在的轻薄本都没有传统的网口,需要上网的时候只能通过一个适配器链接。
类适配器模式
网线:
package com.kuang.Adapter;
public class Adaptee {
public void request(){
System.out.println("连接网线上网");
}
}
转换器:
package com.kuang.Adapter;
public interface NetToUSB {
public void handleRequest();
}
通过接口实现,增强复用
适配器
package com.kuang.Adapter;
public class Adapter extends Adaptee implements NetToUSB{
@Override
public void handleRequest() {
super.request();
}
}
电脑:
package com.kuang.Adapter;
public class Computer {
//我们的电脑需要连接1:转按器才可以1:网
public void net(NetToUSB adapter) {
//.上网的具体实现, 找“个转接义
adapter.handleRequest();
}
public static void main(String[] args) {
//电脑,适配器,网线~
Computer computer = new Computer(); //电脑
Adaptee adaptee = new Adaptee(); //网线
Adapter adapter = new Adapter(); //转按器
computer.net(adapter);
}
}
对象适配器,通过组合的方式:
只需要更改适配器:
package com.kuang.Adapter;
public class Adapter2 implements NetToUSB{
private Adaptee adaptee;
public Adapter2(Adaptee adaptee){
this.adaptee=adaptee;
}
@Override
public void handleRequest() {
adaptee.request();
}
}
package com.kuang.Adapter;
public class Computer {
//我们的电脑需要连接1:转按器才可以1:网
public void net(NetToUSB adapter) {
//.上网的具体实现, 找“个转接义
adapter.handleRequest();
}
public static void main(String[] args) {
Computer computer = new Computer(); //电脑
Adaptee adaptee = new Adaptee(); //网线
Adapter2 adapter = new Adapter2(adaptee); //转按器
computer.net(adapter);
}
}
适配器模式的应用:
DataAdapter 用作DataSet和数据源之间的适配器以便检索和保存数据。DataAdapter通过映射Fill和Update来提供这一适配器。由于数据源的来源可能来自多个数据源,比如SQL Server 可能来自Oracle。这些数据来源不同,但是我们希望得到统一的DataSet,此时用DataAdapter就是非常好的手段,我们不必关心数据库的数据细节,就可以灵活使用数据。
6 桥接模式
桥接模式是将抽象部分和它的实现部分分离,使他们都可以独立的变化。它是一种对象结构型模式,又称为炳体模式或者接口模式。
Brand 类
package com.kuang.bridge;
//品牌
public interface Brand {
void info();
}
Lenovo
package com.kuang.bridge;
//联想品牌
public class Lenovo implements Brand {
@Override
public void info() {
System.out.print("联想");
}
}
Apple
package com.kuang.bridge;
//联想品牌
public class Apple implements Brand {
@Override
public void info() {
System.out.print("苹果");
}
}
Computer
package com.kuang.bridge;
//抽象的电脑类型类
public abstract class Computer {
//组合,品牌~
protected Brand brand;
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.print("台式机");
}
}
class Laptop extends Computer {
public Laptop(Brand brand) {
super(brand);
}
@Override
public void info() {
super.info();
System.out.print("笔记本");
}
}
Test
package com.kuang.bridge;
public class Test {
public static void main(String[] args) {
//苹果管记本
Computer computer = new Laptop(new Apple());
computer.info();
System.out.println();
//联想台式机
Computer computer2 = new Desktop(new Lenovo());
computer2.info();
}
}
运行结果
苹果笔记本
联想台式机
优劣势分析
原理再理解
桥接模式和适配器模式的区别:
共同点
桥接和适配器都是让两个东西配合工作
出发点不同。
1)适配器:改变已有的两个接口,让他们相容。
2)桥接模式:分离抽象化和实现,使两者的接口可以不同,目的是分离。
所以说,如果你拿到两个已有模块,想让他们同时工作,那么你使用的适配器。
如果你还什么都没有,但是想分开实现,那么桥接是一个选择。
桥接是先有桥,才有两端的东西
适配是先有两边的东西,才有适配器
桥接是在桥好了之后,两边的东西还可以变化。
7静态代理模式
代理模式就是SpringAOP的底层
角色分析:抽象角色:一般使用接口或者抽象类来解决
真实角色:被代理的角色
代理角色:代理真实角色,代理真实角色猴,我们一般做一些附属操作
客户:访问代理对象的人
代理模式的好处:
可以使真实角色更加纯粹,不用去关注一些公共的业务
公共业务就可以交给代理角色,实现了业务的分工
公共业务发生扩展的时候,方便集中管理
缺点:
一个真是角色就会产生一个代理角色,代码量会增加,开发效率会变低。
代码步骤:
1 接口:
package com.kuang.staticproxy;
public interface Rent {
void rent();
}
2真实角色
package com.kuang.staticproxy;
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
3代理角色
package com.kuang.staticproxy;
public class Client {
public static void main(String[] args) {
Host host=new Host();
Proxy proxy=new Proxy(host);//代理帮助房东租房子,但是,代理角色会有一些附属操作!这就是面向切面编程
proxy.rent();
}
}
4客户端访问角色
package com.kuang.staticproxy;
public class Proxy implements Rent{
private Host host;
public Proxy(){
}
public Proxy(Host host){
this.host=host;
}
@Override
public void rent() {
seeHouse();
host.rent();
hetong();
fare();
}
public void seeHouse(){
System.out.println("中介带你看房");
}
public void fare(){
System.out.println("收中介费");
}
public void hetong(){
System.out.println("签合同");
}
}
AOP
package com.kuang.staticproxy;
public interface UserService {
void add();
void delete();
void update();
void selete();
}
package com.kuang.staticproxy;
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 selete() {
System.out.println("查找一个对象");
}
}
package com.kuang.staticproxy;
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
public UserServiceProxy(UserServiceImpl userService) {
this.userService = userService;
}
public UserServiceProxy() {
}
@Override
public void add() {
userService.add();
log("add");
}
@Override
public void delete() {
userService.delete();
log("delete");
}
@Override
public void update() {
userService.update();
log("update");
}
@Override
public void selete() {
userService.selete();
log("select");
}
public void log(String name){
System.out.println("调用了"+name+"方法");
}
}
package com.kuang.staticproxy;
public class Client2 {
public static void main(String[] args) {
UserServiceImpl userService=new UserServiceImpl();
UserServiceProxy proxy=new UserServiceProxy(userService);
proxy.add();
proxy.delete();
proxy.selete();
proxy.update();
}
}
8 动态代理
参见之前的博客<狂神说Spring5学习笔记>第10节
9 装饰者模式
由于狂神没有更新相关内容,剩下的内容均来自尚硅谷的韩顺平老师。
星巴克咖啡订单项目 星巴克咖啡订单项目(咖啡馆):
1) 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式 咖啡)、Decaf(无因咖啡)
2) 调料:Milk、Soy(豆浆)、Chocolate
3) 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
4) 使用OO的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖 啡+调料组合
方案1-解决星巴克咖啡订单问题分析
1) Drink 是一个抽象类,表示饮料
2) des就是对咖啡的描述, 比如咖啡的名字
3) cost() 方法就是计算费用,Drink 类中做成一个抽象方法.
4) Decaf 就是单品咖啡, 继承Drink, 并实现cost
5) Espress && Milk 就是单品咖啡+调料, 这个组合很多
6) 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料, 类的数量就会倍增,就会出现类爆炸
方案2-的问题分析
1) 方案2可以控制类的数量,不至于造成很多的类
2) 在增加或者删除调料种类时,代码的维护量很大
3) 考虑到用户可以添加多份 调料时,可以将hasMilk 返回一个对应int
4) 考虑使用 装饰者 模式
9.1装饰者模式定义
装饰者模式定义
1) 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更 有弹性,装饰者模式也体现了开闭原则(ocp)
2) 这里提到的动态的将新功能附加到对象和ocp原则,在后面的应用实例上会以代 码的形式体现,请同学们注意体会。
装饰者模式(Decorator)原理
装饰者模式原理
1) 装饰者模式就像打包一个快递
主体:比如:陶瓷、衣服 (Component) // 被装饰者
包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)
2) Component 主体:比如类似前面的Drink
3) ConcreteComponent和Decorator ConcreteComponent:具体的主体, 比如前面的各个单品咖啡 Decorator: 装饰者,比如各调料.
4) 在如图的Component与ConcreteComponent之间,如果 ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来, 抽象层一个类。
装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
说明
1) Milk包含了LongBlack
2) 一份Chocolate包含了(Milk+LongBlack)
3) 一份Chocolate包含了(Chocolate+Milk+LongBlack)
4) 这样不管是什么形式的单品咖啡+调料组合,通过递归方式可以方便的组合和维护。
package com.kuang.decorator;
public abstract class Drink {
public String des;
private float price=0.0f;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public abstract float cost();
}
package com.kuang.decorator;
public class Coffee extends Drink{
@Override
public float cost() {
return super.getPrice();
}
}
package com.kuang.decorator;
public class Espresso extends Coffee{
public Espresso() {
setDes(" 意大利咖啡 ");
setPrice(6.0f);
}
}
package com.kuang.decorator;
public class ShortBlack extends Coffee{
public ShortBlack() {
setDes(" shortblack ");
setPrice(4.0f);
}
}
package com.kuang.decorator;
public class LongBlack extends Coffee{
public LongBlack() {
setDes(" longblack ");
setPrice(5.0f);
}
}
package com.kuang.decorator;
public class DeCaf extends Coffee{
public DeCaf() {
setDes(" 无因咖啡 ");
setPrice(1.0f);
}
}
package com.kuang.decorator;
public class Decorator extends Drink{
private Drink obj;
public Decorator(Drink obj) { //组合
// TODO Auto-generated constructor stub
this.obj = obj;
}
@Override
public float cost() {
// TODO Auto-generated method stub
// getPrice 自己价格
return getPrice() + obj.cost();
}
@Override
public String getDes() {
// TODO Auto-generated method stub
// obj.getDes() 输出被装饰者的信息
return des + " " + getPrice() + " && " + obj.getDes();
}
}
package com.kuang.decorator;
public class Chocolate extends Decorator{
public Chocolate(Drink obj) {
super(obj);
setDes(" 巧克力 ");
setPrice(3.0f); // 调味品 的价格
}
}
package com.kuang.decorator;
public class Soy extends Decorator{
public Soy(Drink obj) {
super(obj);
// TODO Auto-generated constructor stub
setDes(" 豆浆 ");
setPrice(1.5f);
}
}
package com.kuang.decorator;
public class Milk extends Decorator{
public Milk(Drink obj) {
super(obj);
// TODO Auto-generated constructor stub
setDes(" 牛奶 ");
setPrice(2.0f);
}
}
package com.kuang.decorator;
public class Bar {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
// 1. 点一份 LongBlack
Drink order = new LongBlack();
System.out.println("费用1=" + order.cost());
System.out.println("描述=" + order.getDes());
// 2. order 加入一份牛奶
order = new Milk(order);
System.out.println("order 加入一份牛奶 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 描述 = " + order.getDes());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入一份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入2份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDes());
System.out.println("===========================");
Drink order2 = new DeCaf();
System.out.println("order2 无因咖啡 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 描述 = " + order2.getDes());
order2 = new Milk(order2);
System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes());
}
}