1 设计模式的分类
创造型模式----使对象的创建与使用分离
单例模式 、工厂模式 、 抽象工厂模式 、建造者模式 、原型模式
结构型模式
适配器模式 、 桥接模式 、 装饰器模式 、 组合模式 、外观模式 、享元模式 、 代理模式
行为型模式
模板方法模式 、 命令模式 、迭代器模式 、观察者模式 、中介者模式、备忘录模式 、解释器模式 、状态模式 、策略模式 、责任链模式 、访问者模式 。
2 面向对象(OOP)的七大原则
- 开闭原则 :对扩展开放,对修改关闭 -------在扩展的时候,尽量不要修改原来的代码
- 里氏替换原则 :继承必须确保父类所拥有的性质在子类中任然成立 ---尽量不要改变父类原有的功能,尽量不重写父类的方法。
- 依赖倒置原则 :要面向接口编程,不要面向实现编程
- 单一职责原则 :控制类的粒度大小,将对象解耦,提高其内聚性 ----一个方法尽量只处理一件事情
- 接口隔离原则 : 要为各个类建立它们需要的专用接口
- 迪米特法则 :只与你的直接朋友交谈,不要跟”陌生人“说话---A B C 不要A和C直接交谈 ,要A和B交谈,完后B再和C交谈
- 合成复用原则 :尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
3 单例模式----可以通过反射机制来破坏单例模式
反射破坏不了枚举的单例
解决办法--再私有构造器里面加锁 --并抛出异常
private LazyMain(){
synchronized (LazyMain.class){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
如果两个对象都是通过反射来创建的,那么我们在构造器加锁的同时,在外面定义一个标志位,判断标志位的true 或者flase
private static boolean hzk = false;
private Holder(){
synchronized (LazyMain.class){
if (hzk==false){
hzk = true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
其最重要的一个思想就是构造器私有
- 饿汉式 ---一上来就先把对象创建出来
public class Hungry {
//最重要的一个思想就是构造器私有,那么别人就无法再去创建对象了
private Hungry(){
}
//饿汉式就是一上来就先把对象创建出来,保证是唯一的
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
- 懒汉式
public class LazyMain {
//构造器私有
private LazyMain(){
}
/**
* DCL懒汉式 volatile防止指令重排
*/
private volatile static LazyMain lazyMain;
public static LazyMain getInstance(){
if (lazyMain==null){
synchronized (LazyMain.class){
if (lazyMain==null){
lazyMain = new LazyMain();
}
}
}
return lazyMain;
}
}
- 静态内部类
public class Holder {
//构造器私有了
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER =new Holder();
}
}
常见场景
- Windows的任务管理器
- 数据库的连接池
- 在Servlet编程中,每个servlet都是单例的
- 在Spring中,每个Bean默认就是单例的
4 工厂模式
作用:实现了创建者和调用者分离
详细分类:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
核心本质:
- 实例化对象不使用new ,用工厂方法代替
- 将选择实现类,创建对象统一管理和控制,从而将调用者和实现类解耦
3.1 简单工厂模式(静态工厂模式)
通过接收不同的参数,来返回不同的实例
实现步骤:
-
- 第一步:编写接口
public interface Car {
void name();
}
-
- 第二步:编写几个具体的类,来实现这个公共的接口
public class Tesla implements Car{
@Override
public void name() {
System.out.println("特斯拉!");
}
public class WuLing implements Car{
@Override
public void name() {
System.out.println("五菱宏光!");
}
}
-
- 第三步:创建一个工厂,根据不同的参数,返回不同的实例
public static Car getCar(String car){
if (car.equals("五菱")){
return new WuLing();
}else if (car.equals("特斯拉")){
return new Tesla();
}else {
return null;
}
}
-
- 第四步:测试方法,根据工厂去获取实例
public class Consumer {
public static void main(String[] args) {
//使用工厂模式,不需要管这个东西是咋来的,不需要我们自己去new了
Car 五菱 = CarFactory.getCar("五菱");
Car 特斯拉 = CarFactory.getCar("特斯拉");
五菱.name();
特斯拉.name();
}
}
弊端:增加一个新的产品,需要修改源代码,违反开闭原则,不方便随便添加新的产品。
当没有使用工厂模式的时候,消费者去买车的时候,是直接去new一个车的,
而当我们使用了工厂模式的时候,不需要再去new一个车,这个任务交给了车工厂,我们需要啥车,直接告诉车工厂就可以,解耦合。
3.2 工厂方法模式
使用工厂方法模式,就是不同的车,有不同的工厂,这些车工厂实现一公共的工厂,再编写具体的类,类实现一个公共接口。完后消费者,根据不同的需求去创建不同的工厂,再从工厂里面获得需要的东西
实现步骤
- 第一步 :创建一个共同的类的接口
public interface Car {
void name();
}
- 第二步 :创建几个具体的类,实现这个公共的接口
public class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉!");
}
}
public class WuLing implements Car {
@Override
public void name() {
System.out.println("五菱宏光!");
}
}
- 第三步:创建一个工厂的接口
public interface CarFactory {
Car getCar();
}
- 第四步:创建具体创建对象的工厂,想创建几个就创建几个,工厂和上面的具体对象类相一致。
public class TeslaFactory implements CarFactory{
@Override
public Car getCar() {
return new Tesla();
}
}
public class WuLingFactory implements CarFactory{
@Override
public Car getCar() {
return new WuLing();
}
}
- 第五步:创建消费者,消费者可以直接通过工厂去获取工厂里面的东西
public class Consumer {
public static void main(String[] args) {
Car car = new WuLingFactory().getCar();
Car car1 = new TeslaFactory().getCar();
car.name();
car1.name();
}
【结论】:根据设计原则:工厂方法模式
根据实际业务:简单工厂模式
应用场景
JDBC中的Connection对象的获取
Spring中IOC容器创建管理bean对象
反射中Class对象的newInstance方法
5 抽象工厂模式 ----就是工厂的工厂
围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂
定义: 抽象工厂模式提供了一个创建 一系列相关或者相互依赖对象的接口,无需指定它们具体的类
就好定义一个手机和路由器这俩个接口,这俩个接口可以由不同的厂商实现
适用场景:
- 客户端(应用层)不依赖于产品类实例如何被创建、实现细节等。
-
- 比如:手机这个抽象类,我们不关心它是如何创建的,我们直接可以用就可以了
实现过程:
- 第一 步 :先创建抽象产品的接口 (例如手机),并写上方法
public interface IphoneProduct {
void start(); //开机
void shutdown(); //关机
void callup(); //打电话
void sendSMS(); //发短信
}
- 第二步:创建具体的产品类,实现抽象产品的接口(比如:手机有华为手机、有小米手机)
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("华为发短信");
}
}
- 第三步:定义抽象产品的工厂(产品族工厂)---定义生产哪些东西----生产的东西以属性的方式写入
/**
* 抽象产品工厂
*/
public interface IProductFactory {
//生产手机
IphoneProduct iphoneProduct();
//生产路由器
IRouterProduct routerProduct();
}
- 第四步:定义具体的具体的工厂类,并实现产品族工厂
public class HuaWeiFactory implements IProductFactory{
@Override
public IphoneProduct iphoneProduct() {
return new HuaweiPhone();
}
@Override
public IRouterProduct routerProduct() {
return new HuaweiRouter();
}
}
- 第五步:定义消费者,在消费者那么,先获取工厂类,通过工厂类来获取产品,再来获取产品的属性
System.out.println("==============华为的产品======================");
//先需要一个华为的工厂
HuaWeiFactory huaWeiFactory = new HuaWeiFactory();
//华为工厂生产手机
IphoneProduct iphoneProduct1 = huaWeiFactory.iphoneProduct();
iphoneProduct1.callup();
iphoneProduct1.sendSMS();
//华为工厂生产路由器
IRouterProduct iRouterProduct1 = huaWeiFactory.routerProduct();
iRouterProduct1.openwife();
}
6 建造者模式---将对象的创建和使用分离
建造者模式也属于创建型模式,它提供了一种创建对象的最佳模式(组装的)
主要作用:在用户不知道 对象的建造过程和细节的情况下就可以直接创建复杂的对象
【例子】:工厂模式生产零件 -----通过建造者模式来组装这些零件
7原型模式 ----拷贝出来一个东西
以原来的东西为模板拷贝出来一个新的东西
浅克隆-----某个类实现Cloneable
并重写clone()
完后创建对象以后,调用clone()方法就可以实现
深克隆实现的方法:序列化和反序列化
改造克隆方法----就是在重写的克隆方法里面将对象的属性也进行克隆
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
Video video = (Video) obj;
//将对象的属性也进行克隆
video.createTime = (Date) this.createTime.clone();
return obj;
}
适用地方:就是在spring中的Bean 的范围:有单例模式,还有原型模式---拷贝
原型模式与工厂模式联合使用---就是在创建工厂时候,工厂太复杂,我们可以拷贝这个工厂,就不需要再创建了
public class Bililiil {
public static void main(String[] args) throws CloneNotSupportedException {
//克隆需要一个原型对象
Date date = new Date();
Video video1 = new Video("你好",date);
//video1克隆video2
Video video2 = (Video) video1.clone();
}
}
以上都是创建型模式(与对象的创建有关)
以下都是结构型模式(从程序的结构上实现松耦合)
8 适配器模式、
将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的哪些类可以在一起工作。
假如:网线是一个类(被适配的) ,笔记本是一个类 转接器是一个类 网线想插入电脑但是插不上,需要中间的那个转接器,从而转接器就是适配器
代码实现:
-
- 第一步:创建一个网线,并编写可以上网的方法
public class Adaptee {
public void reuqest(){
System.out.println("连接网线上网");
}
}
-
- 第二步 : 创建一个电脑类,并编写上网的方法,此时上网上不了,需要转接器,那么就把转接器传给电脑这个方法。
public class Computer {
//这需要一个转接头,才可以上网。那么我们就给它传入这个转接头
public void net(NetToUsb adapter){
//上网的实现,想上网,上不了,需要一个转接头
adapter.handleRequest();
}
-
- 第三步:编写适配器(转接器),因为是面向接口编程,所以先创建一个适配器接口,这个接口就是处理请求,把网线插到转接器上
/**
* 面向接口编程,接口就可以生产好多的转接投
*/
public interface NetToUsb {
//处理请求,,把网线插到usb上
public void handleRequest();
}
-
- 第四步:实现这个接口,真正的适配器去继承网线那个类,并调用父类的上网方法,就可以上网了
public class Adapter extends Adaptee implements NetToUsb{
@Override
public void handleRequest() {
super.reuqest(); //可以上网了
}
}
-
- 第五步:编写测试类,创建电脑、网线、转接器(适配器)对象,并调用电脑的上网方法,实现上网。
public static void main(String[] args) {
//上网需要电脑,适配器(转接器) ,网线
Computer computer = new Computer();
Adaptee adaptee = new Adaptee(); //网线
Adapter adapter = new Adapter(); //转接器
computer.net(adapter);
}
}
【注意】适配器有两种模式----推荐使用组合(对象适配器)
-
-
-
- 继承 -----类适配器
-
-
public class Adapter extends Adaptee implements NetToUsb{
@Override
public void handleRequest() {
super.reuqest(); //可以上网了
}
}
-
-
-
- 组合(相当于注入 属性 +构造方法注入) -----对象适配器
-
-
public class Adapter2 implements NetToUsb{
private Adaptee adaptee;
public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void handleRequest() {
adaptee.reuqest(); //可以上网了
}
}
-
-
- 将字节流/字符流转为字符流/字节流
- springBoot中使用
-
9 桥接模式
- 桥接模式是将抽象部分与它的现实部分分离,使它们可以独立地变化。
苹果笔记本 、 苹果台式机 、苹果平板
联想笔记本 、联想台式机 、联想平板
上面这几个它们有类型和品牌两个维度。我们可以将(一个维度)类型创建成为一个类,完后再将(一个维度)品牌创建一个类,完后通过一个桥将这两个维度联系起来
实现过程:
如果是两个维度的话,我们一般都是先创建出来一个维度的接口以及具体的实现类,完后,再创建另一个维度的类,并通过构造方法把那个维度的接口注入进来,这个就是桥
10 代理模式
10.1 静态代理
例如service接口
第一步: 我们先编写一个service接口,并写几个抽象方法
第二步: 编写service的实现类,并实现里面的方法
第三步: 使用Controller里面调用service里面的方法
第四步: 想要增强类,那么就再创建一个增强类,并实现service接口 同时实现方法,同时还把service实现类注入进来,完后通过实现类来调用接口中的方法.
第五步: 在Controller中创建代理对象,并调用代理对象里面的方法.
public class Client(){
public static void main(String[] arg){
UserServiceProxy userServiceProxy =new UserServiceProxy();
userServiceProxy.setUserService(userService);
}
}
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决 (租房的事件)
- 真实角色:被代理的角色 (房东)【一般就是实现事件的接口】
- 代理角色:代理真实的角色,代理真实角色后,我们一般会有增强代码。【代理谁就要把谁组合进来,并实现事件的接口,完后调用真实角色的方法,一般还可以增强】
- 客户:访问代理对象的人 ---(要租房子的人)【一般先创建真实角色,再创建代理角色,创建代理角色的同时,把真实角色传入进去,完后调用代理对象方法】
public interface Rate{
public void rent();
}
public class Host implements Rate(){
public void rent(){
System.out.println("房东要出租房子了")
}
}
代理的人---房子中介 代理房东,那么就把房东组合进去,同时要实现租房子的接口,来实现租房子
public class Proxy implements Rate{
private Host host;
public Proxy(){
}
public Proxy(Host host){
this.host=host;
}
public void rent(){
host.rent() //调用房东的方法去租房子
}
}
public class Client{
public static void main(String[] args){
//创建房东
Host host =new Host();
//再创建代理,并把房东传进去
Proxy proxy =new Proxy(host);
proxy.rent();
}
}
代理角色的好处:
- 可以使真实角色的操作更加纯粹,不需要去关注一些公共的业务
- 公共业务就交给了代理角色,实现了业务分工。
- 公共业务发送扩展的时候,集中管理。
缺点:
- 一个真实角色就会产生一个代理角色。代码量增加.
10.2 动态代理 ---动态代理的本质就是使用反射
ProxInvocationHandler()
就是 调用处理程序并返回一个结果的Porxy ()
生成动态代理实例的
- 动态代理和静态代理的角色是一样的
- 动态代理的代理类是动态生成的,不是我们写好的
- 动态代理分为两大类 : 基于接口的动态代理 ,基于类的动态代理
-
- 基于接口的动态代理----JDBC动态代理
- 基于类动态代理-----cglib动态代理
- java字节码实现 ------javasist
实现过程
第一步: 我们先编写一个service接口,并写几个抽象方法
第二步: 编写service的实现类,并实现里面的方法
第三步: 创建一个类,(这个类就是自动生成的代理类),首先这个类实现InvocationHandler接口,并重写里面的方法,完后再创建一个方法,来生成代理类 .
public class ProxInvocationHandler implement InvocationHandler{
//组合被代理的接口
private Rent rent;
public void setRent(Rent rent){
this.rent = rent;
}
//生成代理类
public Object getProxy(){
//第一个参数:得到类加载器
//第二个参数: 被代理的接口
//第三个参数: InvocationHandler ,当前程序的InvocationHandler就是本身
retuen Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),
this);
}
//处理代理实例,并返回结果
public Object invoke (Object Proxy ,Method method ,Object[] args) throws Throwable{
//执行接口中的方法,参数就是组合进来的接口
Object result = method.invoke(rent,args)
return null;
}
}
第四步:编写Controller来调用
public ststic void mian(String[] args){
//创建真实角色
Host host =new Host();
//创建那个代理程序
ProxInvocationHandler proxInvocationHandler =new ProxInvocationHandler();
//proxInvocationHandler来处理真实角色
proxInvocationHandler.setRent(host)
//生成代理类
Rent proxy = (Rent)proxInvocationHandler.getProxy()
proxy.rent();
}
增强的时候,直接在这个类中编写方法,完后并写入那里面即可.