设计模式
饿汉式加载,在类的初始化的时候就创建一个对象,并且将构造方法私有化,别人不能通过new来进行创建,给外部提供一个getinstance方法,别人可以用这个类的静态方法获取对象,获取的对象都一样,
class Singleton{
//构造器私有化,外部不能new
private Singleton(){
}
//本类内部创建对象实例
private final static Singleton instance = new Singleton();
//对外部提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
第二种饿汉式的方式就是在加载类的时候把对象创建出来
class Singleton{
//构造器私有化,外部不能new
private Singleton(){
}
//本类内部创建对象实例
private static Singleton instance ;
static {//在静态代码块中,创建单例对象
instance = new Singleton();
}
//对外部提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
z
懒汉式创建,使用到的时候才去创建
class Singleton{
private static Singleton instance;
private Singleton(){}
//提供一个静态的公有方法,当使用到该方法时,才去创建instance
//懒汉式
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
这种做法是线程不安全的,因为有可能同时访问这个线程,然后就创建出了两个,
所以说这里有一个解决方法,将getInstance实例化的方法锁起来,当有一个线程访问时,其他线程不允许访问
public static synchronized Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
这样也有一些问题,因为每一个线程访问的时候都要被锁起来,很慢,
有人想出了解决的办法,当实例为null的时候锁起来,这样其实是错误的,因为这并没有解决问题,,当其他的线程进入if里面之后还是会造成线程安全的问题,还不如不进行改进,这是错误的
还有一种是使用静态内部类来进行写,这运用到了jvm虚拟机的性质,所以这个也是安全的,推荐使用这个
外部类的装载不会导致静态内部类的装载,当加载静态内部类时线程安全
class Singleton{
private static volatile Singleton instance;
//构造器私有化
private Singleton(){}
//写一个静态内部类,该类中有一个静态属性Singleton
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态的公有方法,
public static synchronized Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
还可以使用枚举来实现单例模式,不仅能避免多线程问题,还能防止反序列化重新创建新的对象
//使用枚举,可以实现单例,推荐
enum Singleton{
INSTANCE;
public void sayOK(){
System.out.println("ok~");
}
}
单例模式的注意事项
单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建和销毁的对象,使用单例模式可以提高系统性能,
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
单例模式的使用场景:需要频繁的进行创建和销毁对象,创建对象时耗时过多或耗费资源过多,
简单工厂模式
使用一个类,来专门的根据传入的条件创建某一个对象,这样可以简化开发,可以将这个工厂类的方法设置成静态的,
public class SimpleFactory {
public Pizza createPizza(String orderType) {
System.out.println("使用简单工厂模式");
Pizza pizza = null;
if (orderType.equals("greek")) {
pizza = new GreekPizza();
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
} else if (orderType.equals("pepper")) {
pizza = new PepperPizza();
}
pizza.setName(orderType);
return pizza;
}
}
工厂方法
我们的工厂已经可以创建实例了,但是需要传递不同的参数才能构建出不同的实例,并且不同的实例属性也有所不同,也就是我们需要根据条件使用工厂创建不同的实例对象,这些实例对象有相同的也有不同的,
这个时候可以将工厂做成一个抽象类,然后再写具体的实例工厂,继承这个抽象工厂,然后实现里面的方法,
public abstract class OrderPizza {
//定义一个抽象方法,cratePizza,让工厂子类自己实现
abstract Pizza createPizza(String orderType);
//构造器
public OrderPizza (){
Pizza pizza = null;
String orderType ;//订购pizza的类型
do {
orderType = getType();
pizza = createPizza(orderType); //抽象的方法,由工厂子类来实现
//输出pizza制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}while (true);
}
}
子类工厂中根据传递的参数去进行判断再创建不同的工厂
抽象工厂方法,
这是前两种工厂的集合,
我们将工厂抽象成一个接口,里面有方法,具体的工厂都继承这个接口,然后我们使用的时候传入不同的工厂对象的继承对象,会生成不同的实例,
public class OrderPizza {
AbsFactory factory ;
public OrderPizza (AbsFactory factory){
this.setAbsFactory(factory);
}
private void setAbsFactory(AbsFactory factory) {
Pizza pizza = null;
String orderType = "";
this.factory = factory;
do {
orderType = getType();
//factory可能是北京的工厂子类,也可能是其他的,
pizza = factory.createPizza(orderType); //抽象的方法,由工厂子类来实现
if (pizza!=null){//订购ok
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购失败");
break;
}
}while (true);
}
}
calendar就是运用了简单的工厂模式
工厂模式小结
工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一的进行管理和维护,进行解耦,从而提高了项目的扩展性和维护性
创建对象实例时,不要直接new类,而是把这个new类的动作放到一个工厂的方法中并返回,
不要让类继承具体类,而是继承抽象类或者是实现接口,
不要覆盖基类中已经实现的方法
简单工厂模式,工厂方法模式,抽象工厂模式
原型模式
当我们想要对一个对象创建很多次元素是,我们可以不用new很多次,而是将我们的类实现cloneable方法,然后进行拷贝,
spring中bean的创建就是根据这一个原理进行创建的,
但是这是浅拷贝,对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象,
但是对于数据类型是引用数据类型的成员变量,比如成员中的某个数组,某个类的对象等,浅拷贝只是会进行引用传递,也就是只是将该成员变量的引用值复制一份给新的对象,许多个成员变量的这个属性都是同一个对象,然后我们在一个复制的对象中修改它的这个属性,其他的复制的这个属性对象也会进行改变
浅拷贝是使用默认的clone方法来实现
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (Exception e){
System.out.println(e.getMessage());
}
return sheep;
}
有的时候我们需要进行深拷贝:
复制对象的所有基本数据类型的成员变量值,
为所有引用数据类型的成员变量申请存储空间,如果引用数据类型中还引用了对象,也进行申请存储空间存储,直到该对象可达的所有对象,也就是说,深拷贝要对整个对象进行拷贝
深拷贝实现方式
重写clone方法,
//完成深拷贝--方式1:使用clone
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
deep = super.clone();//先完成对基本数据类型和字符串的克隆
//对引用类型的属性进行单独的处理
DeepProtoType deepProtoType = (DeepProtoType) deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deepProtoType;
}
对象序列化
//深拷贝 - 方式2,通过对象的序列化来实现(推荐使用)
public Object deepClone(){
DeepProtoType copy = null;
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);//把当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
copy = (DeepProtoType) ois.readObject();
return copy;
} catch (Exception e){
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
原型模式
创建新的对象比较复杂时,可以提高效率,
不用重新初始化对象,而是动态的获得对象运行时的状态
原始对象发生变化,其他的克隆对象也会发生相应的变化,无需修改代码
实现深克隆的时候代码比较复杂,
缺点:每一个类都需要有一个克隆方法,如果时维护的话,不好维护
建造者模式
可以将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方法可以构造出不同表现的对象
四个角色
Product(产品角色)
public class House {
private String base;
private String wall;
private String roofed;
}
Builder (抽象建造者)
public abstract class HouseBuilder {
protected House house = new House();
//将建造的流程写好,抽象的方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
//建造房子
public House buildHouse(){
return house;
}
}
ConcreteBuilder(具体建造者)
public class CommonHouse extends HouseBuilder {
public void buildBasic() {
System.out.println("普通房子地基");
}
public void buildWalls() {
System.out.println("普通房子砌墙");
}
public void roofed() {
System.out.println("普通房子屋顶");
}
}
Director(指挥者)
public class HouseDirector {
HouseBuilder builder = null;
//构造器传入
public HouseDirector(HouseBuilder builder) {
this.builder = builder;
}
//setter方法引入
public void setBuilder(HouseBuilder builder) {
this.builder = builder;
}
//如何处理建造房子的流程,交给指挥者
public House constructorHouse(){
builder.buildBasic();
builder.buildWalls();
builder.roofed();
return builder.buildHouse();
}
}
具体的方式是:指挥者指挥具体的建造者,使用它们的方法对产品进行建造,然后返回一个产品对象
建造者模式的注意事项和细节
客户端不必知道产品内部组成的细节,将产品本身和产品的创建过程进行解耦,使得相同的创建过程可以创建不同的产品对象
每一个具体的建造者都相对独立,而且与其他的具体建造者无关,用户使用不同的具体建造者可以得到不同的产品对象
可以更加精细地控制产品的创建过程
增加新的具体建造者无需修改原有类库的代码
建造者模式所创建的产品一般具有较多的共同点,如果产品的差异性很大,则不适合建造者模式
类适配器模式
当我们想要使用一个类里面的方法,但是又不能直接使用的时候,需要对这个类里面的方法进行一些修改,
这个时候运用到了适配器模式,
我们想要使用外部类的什么方法,可以先定义一个接口,声明一下这个方法,
public class Voltage220 {
//输出220v电压
public int output220V(){
int src = 220;
System.out.println("电压"+src+"V");
return src;
}
}
public interface Voltage5v {
public int output5V();
}
接下来就是特殊的地方了:我们定义了一个适配器类,这个适配器类继承外部类,并实现我们定义的接口,
public class VoltageAdapter extends Voltage220 implements Voltage5v {
public int output5V() {
//获取到220v的电压
int srcV = output220V();
int dstV = srcV/44;
return dstV;
}
}
继承外部类就可以使用外部类的方法,实现接口就需要重写方法,我们就把外部类改造为需要的方法,然后
使用的时候直接使用接口即可
public class Phone {
//充电
public void charging(Voltage5v voltage5v){
if (voltage5v.output5V()==5){
System.out.println("电压5V,可以充电");
} else if (voltage5v.output5V()>5){
System.out.println("大于5v,不能充电");
}
}
}
这样的弊端:
java是单继承的,所以类适配器需要继承src类有点缺陷,
src类的方法在适配器类中都会暴露出来,增加了使用的成本
由于继承了src类,所以它可以根据需求重写src类的方法,使得适配器的灵活性增强了
对象适配器模式
基本思路和类适配器相同,只是将适配器类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题,即持有src类,实现dst接口,完成
根据合成复用原则,系统中尽量使用关联关系,来替代继承关系,
对象适配器模式是适配器模式常用的一种,
总的来说就是把原来的继承改变为了聚合
其他的都没有变只是适配器变了一下
private Voltage220 voltage220;
public VoltageAdapter(Voltage220 voltage220) {
this.voltage220 = voltage220;
}
public int output5V() {
int dst = 0;
if (null!=voltage220){
//获取到220v的电压
int srcV = this.voltage220.output220V();
System.out.println("使用对象适配器进行转换");
dst = srcV/44;
}
return dst;
}
接口适配器模式
当不需要全部实现接口提供的方法时,可以先设计一个抽象类实现接口,并为该接口中每一个方法提供一个默认实现,该抽象类的子类可有选择的覆盖父类的某些方法来实现需求
适用于一个接口不想使用其所有的方法的情况
public abstract class AbsAdapter implements Interface4 {
public void m1() {
}
public void m2() {
}
public void m3() {
}
public void m4() {
}
}
这样就可以使用了
AbsAdapter adapter = new AbsAdapter() {
@Override
public void m1() {
System.out.println("使用了m1的方法");
}
};
adapter.m1();
使用哪个方法就重写哪个方法即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0BS78dXY-1596449378182)(图片文件/1583307476712.png)]
springMVC中的模式就是我们得到了controller,然后需要handler去实现但是我们不知道使用哪一个去实现,这个时候使用一个中间的桥梁,DispatchServlet去处理,里面得到所有的adapter,当一个controller请求过来的时候,我们进行判断,如果找到了合适的就返回给它去处理
桥接模式
将实现和抽象放在两个不同的类层次中,使两个类可以进行独立的改变
让不同的类承担不同的职责,把抽象和行为分离,从而可以保持各部分的独立性以及应对它们的性能扩展
类图:
client:桥接模式的调用者
抽象类(Abstraction):维护了Implementor/即具体的实现类,是聚合的关系
抽象类子类
实现类:行为实现类的接口
conCreteImplementorA:行为的具体实现类
public interface Brand {
void open();
void close();
void call();
}
public abstract class Phone {
//组合品牌
private Brand brand;
public Phone(Brand brand) {
this.brand = brand;
}
protected void open(){
this.brand.open();
}
protected void close(){
this.brand.close();
}
protected void call(){
this.brand.call();
}
}
public class ViVo implements Brand {
public void open() {
System.out.println("ViVo手机开机");
}
public void close() {
System.out.println("ViVo手机关机");
}
public void call() {
System.out.println("ViVo手机打电话");
}
}
public class FoldedPhone extends Phone {
//构造器
public FoldedPhone(Brand brand) {
super(brand);
}
public void open(){
super.open();
System.out.println("折叠样式手机");
}
public void close(){
super.close();
System.out.println("折叠样式手机");
}
public void call(){
super.call();
System.out.println("折叠样式手机");
}
}
public class Client {
public static void main(String[] args) {
//获取折叠式手机(样式加品牌)
Phone phone1 = new FoldedPhone(new ViVo());
phone1.open();
phone1.call();
phone1.close();
}
}
桥接模式的适用性比较局限,主要是想把两个维度分开,
常用场景:
JDBC驱动程序,银行转账,消息管理
难点就是:需要根据应用场景来区分哪些是抽象的,哪些是实现的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4qlfrLb-1596449378187)(图片文件/1583328896045.png)]
装饰者模式
装饰者模式适合于双方都是比较多的选择的情况下
首先定义一个抽象类
public abstract class Drink {
private String des;
private float price = 0.0f;
//计算费用的方法,交给子类实现
public abstract float cost();
}
然后定义一个接口去实现
public class Coffee extends Drink {
public float cost() {
return super.getPrice();
}
}
具体的子类来实现这个抽象类,并且再构建子类的同时,加入一些这个子类特有的属性,比如我们可以得到这个Drink的描述,这是一个意大利咖啡,价值6元,然后Drink里面就有了类似的属性
public class Espresso extends Coffee {
public Espresso() {
setDes("意大利咖啡");
setPrice(6.0f);
}
}
然后我们开始写装饰者,装饰者还是会继承Drink因为装饰者装饰完了之后也是一个Drink然后里面还需要聚合一个Dribk类型的对象,就在构造方法里面进行聚合
public class Decorator extends Drink {
private Drink obj;
public Decorator(Drink obj) {
this.obj = obj;
}
public float cost() {
//修饰的价格加上原来的价格
return this.getPrice()+obj.cost();
}
@Override
public String getDes() {
return super.getDes()+" "+super.getPrice()+"&&"+obj.getDes();
}
}
然后装饰的时候就是把价格和描述进行一些修改,然后装饰者把父类中的方法进行重写,然后返回一个装饰者的对象,这个对象的父类也是Drink,所以它也可以被聚合,然后再产生新的装饰者
public static void main(String[] args) {
Drink order = new LongBlack();
order = new Milk(order);
System.out.println(order.getDes());
System.out.println(order.cost());
System.out.println(order.getPrice());
order = new Chocolate(order);
System.out.println(order.cost());
System.out.println(order.getDes());
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PfDFJNiL-1596449378191)(图片文件/1583376551016.png)]
组合模式
组合模式的角色及职责
component:组合中对象声明接口,在适当情况下,实现所有类公有的默认接口行为,访问和管理component子组件,component可以是抽象类或者接口
leaf:在组合中表示叶子节点,叶子节点没有子节点
composite:非叶子节点,用于存储子组件,在coponent接口中实现子部件的相关操作,比如增加
首先建立一个抽象类,在里面定义一些基本的方法,抽象类中可以定义一些子类必须要有的抽象方法,也可以将方法默认实现,因为有的子类可以没有这个方法
public abstract class OrganizationComponent {
private String name;//名字
private String des;//说明
}
然后我们需要组合的子组件都要继承我们的抽象类,可以自己写一些方法,也可以自己添加一些属性,因为都是对被组合的其他类进行操作,所以可以操控其他的继承类
public class College extends OrganizationComponent {
//这个list中存放的为系
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
public College(String name, String des) {
super(name, des);
}
}
然后我们就可以根据组合在一起的情况进行操作了
组合模式的注意事项和细节
客户端只需要面对一致的对象而不用考虑到部分或节点叶子的问题
具有较强的扩展性,当我们要更改组合对象时,只需要调整内部的层次关系,
方便创建复杂的层次结构,容易操作复杂的树形结构
遍历组织结构或者处理的对象具有树形结构时,比较适合使用组合模式
如果节点和叶子有很多差异性的话,不适合使用组合模式
组合模式使得用户对单个对象和组合对象的访问具有一致性,
外观模式
外观就是需要其中的一个类把其他的接口中的方法整合起来,然后外观类在构造的适合把其他的类进行注入,对这些不同的类中的方法进行整合,然后把固有的流程合并为一个方法,
public class HomeTheaterFacade {
//定义各个子系统的对象
private TheaterLight theaterLight;
private Popcorn popcorn;
private DVDPlayer dvdPlayer;
private Stereo stereo;
private Projector projector;
private Screen screen;
public HomeTheaterFacade() {
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.dvdPlayer = DVDPlayer.getInstance();
this.stereo = Stereo.getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
}
//操作分成4步
public void ready(){
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
stereo.on();
dvdPlayer.on();
theaterLight.dim();
}
}
外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易于维护和扩展
帮助我们更好的划分访问层次
享元模式
共享对象,数据库连接池等,常量池
享元模式,两个要求:细力度和共享对象,把对象的信息分为两个部分:内部状态和外部状态
内部状态:对象共享的信息,不会随环境的改变而改变
外部状态:随环境改变而改变,不可共享
注意事项和细节
系统中有大量对象,这些对象占用大量内存,并且对象大部分可以外部化时,可以考虑使用享元模式
用一个唯一i标识码判断,在内存中有就返回标识码所标识的对象,一般都是使用HashMap或者HashTable存储
享元模式减少了对象的创建,降低程序内存
但是提高了系统的复杂度,需要区分哪些时内部状态,哪些是外部状态,需要一个工程类来进行控制
缓冲池,数据库连接池
在例子中type是内部的状态,因为所有的网站都有一个type
public abstract class WebSite {
public abstract void use(User user);
}
public class ConcreteWebSite extends WebSite {
private String type = "";//网站发布的形式
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的发布形式为"+type+" 使用者为"+user.getName());
}
}
具体的网站来继承网站的抽象类,所有的网站都有类型,类型不同,但是基本上不会发生变化,而我们使用的时候会有所不同,用户不同,信息也不同
public class WebSiteFactory {
//集合,充当池的作用
private HashMap<String,ConcreteWebSite> pool = new HashMap<>();
//根据网站的类型,返回一个网站,如果没有就创建一个网站,并放入到池中,
public WebSite getWebSiteCategory(String type){
if (!pool.containsKey(type)){
//如果没有就创建一个bean放入池中
pool.put(type,new ConcreteWebSite(type));
}
return (WebSite) pool.get(type);
}
//获取网站分类总数,(池中有多少各网站类型)
public int getWebSiteCount(){
return pool.size();
}
}
相当于把所有的网站使用都放到pool里面,我们使用的时候去这里面去找然后返回
public static void main(String[] args) {
//创建一个工厂类
WebSiteFactory factory = new WebSiteFactory();
//客户要一个以新闻形式发布的网站
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User().setName("曹帅"));
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User().setName("杨梦"));
int count = factory.getWebSiteCount();
System.out.println(count);
}
代理模式
静态代理
优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能进行扩展
缺点:代理对象需要与目标对象实现一样的接口,所以会有很多代理类,
一旦接口增加方法,目标对象与代理对象都要维护
public interface ITeacherDao {
public void teach();
}
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师授课中");
}
}
public class TeacherDaoProxy implements ITeacherDao {
private ITeacherDao target;//这是目标对象
//构造器
public TeacherDaoProxy (ITeacherDao target){
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理");
target.teach();
System.out.println("代理结束");
}
}
public static void main(String[] args) {
//创建目标对象,被代理对象
TeacherDao teacherDao = new TeacherDao();
//创建代理对象
TeacherDaoProxy proxy = new TeacherDaoProxy(teacherDao);
proxy.teach();
}
这是让我们的工厂进行代理,我们创建代理对象时,把原来的对象传入进去,在代理对象中定义和被代理对象一样的方法,然后加入一些代理要做的工作,然后用户使用就可以直接使用代理对象的方法了
动态代理对象
主要时使用了JDK自带的反射机制
public Object getProxyInstance (){
//参数1:指定当前目标对象使用的类加载器,获取加载器的方法固定
//参数2:目标对象它实现的接口类型,使用泛型方式确认类型
//参数3:InvocationHandler:事件处理,执行目标对象方法时,触发事件处理器方法
//会把当前执行的目标对象方法作为一个目标参数传入
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理");
//反射调用目标对象方法
Object result = method.invoke(target,args);
return result;
}
});
}
代理对象还是接收一个被代理的对象,然后给出一个返回对象实例的方法,内部是使用jdk自带的Proxy对象的方法,具体是传入类加载器,目标类对象实现的所有接口,具体的方法
这里有一个好处就是不需要专门往代理对象中写方法,直接在newProxyInstance方法中就直接定义好了
Cglib代理
原理是在内存中动态的创建子类,但是被代理的类不能是final/static,因为这样就不能生成子类了
使用cglib代理时,被代理的对象不需要实现接口也可以,需要导入jar,这个的原理好像是复制字节码来实现的
public class ProxyFactory implements MethodInterceptor {
//维护一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//返回一个代理对象,是target的代理对象
public Object getProxyInstance(){
//1.创建一个工具类,
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(target.getClass());
//3.设置回调函数
enhancer.setCallback(this);
//4.创建子类对象,即代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理开始");
Object result = method.invoke(target, args);
return result;
}
}
代理模式的变体:
内网通过代理穿透防火墙,实现对公网的访问,
缓存代理,请求图片文件时,先到缓存代理取,如果取到资源ok,取不到再去公网或者数据库
远程代理:
同步代理:多线程编程中,同步的工作
模板方法
在一个抽象类中公开定义了执行它的方法的模板,它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行
模板方法定义了一个操作中的算法的骨架,而将一些步骤延迟到了子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
行为性模式
在这里定义了算法的步骤,一般不需要改变的直接写入方法内容,在子类中需要改变的定义为抽象方法,可以重写里面的内容,达到每个子类都按照一定的步骤,但是因为输入内容不同而结果也不同的情况,将整合的方法定义为final,不能被修改
public abstract class SoyaMilk {
final void make(){
select();
addCondiments();
soak();
beat();
}
void select(){
System.out.println("1.选择黄豆");
}
//添加标不同的配料
abstract void addCondiments();
//浸泡
void soak(){
System.out.println("3.黄豆和配两开始浸泡");
}
void beat(){
System.out.println("4.放入豆浆机");
}
}
模板方法模式的钩子方法
如果我们不想走这个步骤也很简单,可以在抽象类中再定义一个方法,判断是否执行即可,默认为true,再子类的实现中修改为false即可不执行了
算法只存在于一个地方,也就是在父类中,容易修改,需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
实现了最大化代码复用,
统一了算法,提供了很大的灵活性,
使用场景:当要完成在某个过程中,该过程执行了一系列步骤,这一系列步骤基本相同,但个别步骤在实现时可能不同,
命令模式
命令模式使得请求发送者和请求执行者之间相互进行解耦
三个角色:
Invoker:调用者
Command:命令角色
Receiver:接收者
ConcreteCommand:将一个接收者对象于一个动作绑定,调用接收者实现
public interface Command {
public void execute();
public void undo();
}
有很多命令,这些命令都在实现了这个接口,具体的操作都不同,1具体操作是接收到一个具体的操作对象,然后使用这个对象的方法进行操作
public class LightOnCommand implements Command {
//集合LightReceiver
private LightReceiver lightReceiver;
public LightOnCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.on();
}
@Override
public void undo() {
lightReceiver.off();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PvbAlsqo-1596449378198)(图片文件/1583652109338.png)]
具体的流程图如上
实际的操作类,这里就很简单
public class TVReceiver {
public void on() {
System.out.println(" 电视机打开了 ");
}
public void off (){
System.out.println("电视机关闭了");
}
}
然后是控制类,控制类比较复杂,需要创建一个命令的数组,然后对这些数组进行初始化,并且设置注入命令的方法,初始化的时候所有的命令都是空命令,这样比较简单
public class RemoteController {
//开 按钮的命令数组
Command[] onCommands;
Command[] offCommands;
//执行撤销的命令
Command undoCommand;
//构造器,完成对按钮的初始化
public RemoteController (){
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0; i < 5; i++) {
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
//给我们的按钮设置你需要的命令
public void setCommand(int no,Command onCommand,Command offCommand){
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}
public void onButtonWasPushed(int no){
//找到你按下的按钮,执行方法
onCommands[no].execute();
//记录这次的操作,用于撤销
undoCommand = onCommands[no];
}
public void offButtonWasPushed(int no){
offCommands[no].execute();
undoCommand = offCommands[no];
}
public void undoButtonWasPushed(){
undoCommand.undo();
}
}
客户端可以创建使用了
//c创建电灯的对象
LightReceiver lightReceiver = new LightReceiver();
//创建电灯相关的开关命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
//遥控器
RemoteController remoteController = new RemoteController();
//给遥控器设置相关命令
remoteController.setCommand(0,lightOnCommand,lightOffCommand);
System.out.println("------开灯--------");
remoteController.onButtonWasPushed(0);
先把具体的操作对象创建完毕,然后注入命令对象的构建方法中得到命令对象
初始化控制对象,并经命令设置到其中
访问者模式
角色及其职责:
visitor:抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit操作
ConcreteVisitor:具体的访问者,实现每个有Visitor声明的操作,是每个操作实现的部分,
ObjectStructure:能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素
Element:定义了一个accept方法,可以接收访问者对象
ConcreteElement为具体元素,实现了accept方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rEaFmSlr-1596449378202)(图片文件/1583656701518.png)]
public abstract class Action {
//得到男性的测评
public abstract void getManResult (Man man);
//得到女性的测评
public abstract void getWomenResult(Women Women);
}
public class Fail extends Action {
@Override
public void getManResult(Man man) {
System.out.println(" 男性评价失败 ");
}
@Override
public void getWomenResult(Women Women) {
System.out.println(" 女性评价失败 ");
}
}
public abstract class Person {
//提供一个方法,让访问者可以访问
public abstract void accept (Action action);
}
public class Man extends Person {
@Override
public void accept(Action action) {
action.getManResult(this);
}
}
public class ObjectStructure {
//维护了一个集合
private List<Person> persons = new LinkedList<>();
//增加到list
public void attach (Person p){
persons.add(p);
}
//移除
public void detach (Person p){
persons.remove(p);
}
//显示测评情况
public void display (Action action){
persons.forEach(p->p.accept(action));
}
}
这个访问者依赖操作对象,操作对象使用方法也需要访问者的传入
适用情况:如果一个系统有比较稳定的数据结构,又有经常变化的需求
迭代器模式
角色:
Iterator:迭代器接口,系统提供的,
ConcreteIterator:具体的迭代器类,管理迭代
Aggregate:一个统一的聚合接口,将客户端和具体聚合解耦
ConcreteAggregate:具体的聚合,持有对象的集合
提供了一个统一的方法遍历对象,客户不用再考虑聚合的类型,适用一种方法就可以遍历对象了
隐藏了聚合的内部结构,客户端遍历聚合的时候只要取到迭代器就可以了,而不会知道聚合的具体组成
一个类应该只有一个引起变化的原因(单一责任原则),把管理对象的责任和遍历对象集合的责任分开
具体的做法是,对需要遍历的对象抽取出来,抽取成一个即可,所以接口中定义一个返回迭代器的方法,具体的实现子类可以对这个返回迭代的方法进行一些修改,迭代时jdk中自由的,具体的子类,再创建时需要实现迭代器的接口,最后适用构造方法把我们子类中的数据传入进去,返回一个迭代器的对象,
不仅需要子类,还需要迭代器接口的子类,专门创建了一个迭代器子类来实现迭代器接口,声明对象,并且实现方法,不同的数据结构有不同的实现方法
public class ComputerCollegeIterator implements Iterator {
//这里我们需要直到Department是以怎样的方式存放的
Department[] departments;
int position = 0;//遍历的位置
//构造方法得知对象
public ComputerCollegeIterator(Department[] departments) {
this.departments = departments;
}
//判断是否还有下一个元素
@Override
public boolean hasNext() {
if (position >= departments.length || departments[position] == null) {
return false;
} else {
return true;
}
}
@Override
public Object next() {
Department department = departments[position];
position += 1;
return department;
}
@Override
public void remove() {
}
}
观察者模式
如果需要做一个推送:
传统的方式是,如果你要看我的信息,那么我就要维护你,数据类中有一个对象,
可以调用对象中的方法,然后对象就可以获取到数据信息了,
当数据更新时,主要第哦啊有对象中的方法,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GnJrlPN8-1596449378205)(图片文件/1583744300346.png)]
发布者来维持接收者
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver();
}
创建子类实现接口,并且实现里面的方法
public class WeatherData implements Subject {
private float temperature;
private float pressure;
private float humidity;
//观察者集合
private ArrayList<Observer> observers;
}
接收者定义更新的方法,接收到了之后使用这个方法把内容进行更新
public interface Observer {
public void update(float temperature, float pressure, float humidity);
}
public class CurrentConditions implements Observer {
private float temperature;
private float pressure;
private float humidity;
public void update (float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
public void display() {
System.out.println(temperature+" - "+pressure+" - "+humidity);
}
}
中介者模式
当我们要做一件事的时候通常比较复杂,要逐层找好多,非常麻烦,我们找A,A取找B,这个时候可以使用中介者模式,我们只需要去找中介,中介去找AB,返回结果
中介者模式:用一个中介对象来封装一系列的对象交互,中介者使各个对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
中介者模式属于行为模式,使代码易于维护,
MVC模式中C就是相当于一个中介,M和V,在前后端交互时起到了中间人的作用
角色介绍:
Mediator:抽象中介者,定义了同事对象到抽象对象的接口
Colleague:是抽象同事类,
ConcreteMediator:具体的中介者对象,实现抽象方法,它需要知道所有的具体的同时类,使用一个集合来管理这些同事类
ConcreteColleague:具体的中介者对象,具体的同事类,每一个同事只知道自己的行为,但是它们都依赖中介者对象
多个类相互耦合,会形成网状结构,使用中介者模式将网状结构分离为星星结构,
减少类之间的依赖,降低了耦合,符合迪米特原则
中介者承担责任较多
备忘录模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态
备忘录对象主要是用来记录一个对象的某一个状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
备忘录模式属于行为模式
角色说明:
Originator:需要保存状态的对象,
Memento:备忘录对象,负责保存好记录,即Originator的内部状态,
Caretaker:守护者对象,负责保存多个备忘录对象,使用集合管理,提高效率
说明:如果希望保存多个originator对象的不同实际的状态,可以使用map
想要保存的对象的实例
public class Memento {
//攻击力
private Integer vit;
//防御力
private Integer def;
public Memento(Integer vit, Integer def) {
this.vit = vit;
this.def = def;
}
}
需要保存状态的对象
public class GameRole {
private Integer vit;
private Integer def;
//创建memento,根据游戏角色的状态得到Memento
public Memento createMemento() {
return new Memento(vit,def);
}
//从备忘录对象恢复GameRole状态
public void recoverGameRoleFromMemento(Memento memento){
this.vit = memento.getVit();
this.def = memento.getDef();
}
//显示当前游戏角色的状态
public void display(){
System.out.println("攻击力:"+this.vit);
System.out.println("防御力:"+this.def);
}
}
再这里有一个方法,可以创建需要备份的对象
public class Caretaker {
//如果只保存一次状态
private Memento memento;
//对GameRole多次保存
//private ArrayList<Memento> mementos;
//对多个游戏角色保存多个状态
//private HashMap<String,ArrayList<Memento>> rolesMementos;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
这个是存储状态的类,可以是集合,也可以是单个的状态,看需求
public static void main(String[] args) {
GameRole gameRole = new GameRole(100, 20);
System.out.println("战斗之前:");
gameRole.display();
//把当前状态保存到caretaker
Caretaker caretaker = new Caretaker();
caretaker.setMemento(gameRole.createMemento());
System.out.println("和boss大战");
gameRole.setVit(80);
gameRole.setDef(10);
gameRole.display();
System.out.println("大战之后恢复");
gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
gameRole.display();
}
给用户提供了一种可以恢复状态的机制,可以比较方便的回到某个历史状态
实现了信息的封装,使得用户不需要关心状态的保存细节
如果类的成员变量过多,势必会占用比较大的资源
解释器模式
在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树,,
解释器模式:指给定一个语言表达式,定义它的文法的一种表示,并定义一个解释器,,使用该解释器来解释语言中的句子
可以将一个需要解释执行的语言中的句子表示为一个抽象语法树,
一些重复出现的问题可以用一种简单的语言来表达,
角色:
context:是环境角色,含有解释器之外的全局信息
abstractExpression:抽象表达式,声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享
TerminalExpression:终结符表达式,实现与文法中的终结符相关的解释操作,
NoneTerminalExpression:为非终结表达式,为文法中的非终结符实现解释操作,
说明:输入context和terminalExpression信息通过Client输入
状态模式
Context:环境角色,用于维护state实例,这个实例定义当前状态,
state:抽象状态角色,定义一个接口封装与Context一个特点接口相关行为
ConcteteState:具体的状态角色,每个子类实现一个与context的一个状态相关行为
这个相当于是一个状态的管理类
public class RaffleActivity {
// state 表示活动当前的状态,是变化
State state = null;
// 奖品数量
int count = 0;
// 四个属性,表示四种状态
State noRafflleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseState = new DispenseState(this);
State dispensOutState = new DispenseOutState(this);
//构造器
//1. 初始化当前的状态为 noRafflleState(即不能抽奖的状态)
//2. 初始化奖品的数量
public RaffleActivity( int count) {
this.state = getNoRafflleState();
this.count = count;
}
//扣分, 调用当前状态的 deductMoney
public void debuctMoney(){
state.deductMoney();
}
//抽奖
public void raffle(){
// 如果当前的状态是抽奖成功
if(state.raffle()){
//领取奖品
state.dispensePrize();
}
}
}
public abstract class State {
public abstract void deductMoney();
public abstract boolean raffle();
public abstract void dispensePrize();
}
状态模式
代码具有很强的可读性,状态模式将每个状态的行为封装到对应的一个类中
策略模式
定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
该算法体现的原则:
把变化的代码从不变的代码中分离,
针对接口编程而不是具体类,多用组合聚合,少用继承
策略模式的意义就是每一个在父类和子类之间有一些方法,原本的做法是子类继承父类,然后重写父类中的方法或者默认父类的方法,
策略模式做法是把方法固定下来,每一个方法的类型都有一个接口,然后方法的可能情况都会继承这个接口,然后实现,也就是说把每一个不确定的方法都抽象为一个接口,我们需要什么类型的方法实现,就去实现把这个方法实现的类赋值到对象里面就可以了,
并且这样的做法的一个好处是还可以进行动态的改变
public abstract class Duck {
//策略接口
FlyBehavior flyBehavior;
public Duck(){
}
public abstract void display();//显示鸭子信息
public void swim() {
System.out.println("鸭子再游泳");
}
public void quack(){
System.out.println("鸭子在叫");
}
public void fly(){
if (flyBehavior!=null){
flyBehavior.fly();
}
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
}
职责链模式
使得多个对象都有机会处理这一请求从而避免请求的发送者和接收者之间的耦合关系,将这个对象连接成一条链,并沿着这条链处理请求直到有一个对象处理它为止
Handler:抽象的处理者,定义了一个处理请求的接口,同时含有另外的Handler接口
ConcreteHandlerA,B是具体的处理着,处理它自己负责的请求,可以访问它的后继者,如果可以处理则处理,不能处理就交给下一个,从而形成一个职责链
Request:含有很多属性,表示一个请求