OOAD——面向对象的分析与设计

内容概括

1、软件工程的概念

整个软件开发行业里,很多概念都是来自于建筑工程的。因为,在人类活动里,这两个行为都有很大的共性,特别是都需要合作和分工。

软件危机

软件危机的出现,导致了软件开发的工程化。软件危机和核心原因是软件规模的变大,复杂度的增加,导致了软件的开发人员凭个人能力难以控制。所以,必须形成一整套科学的分析和管理方案。慢慢的,引入了工程学概念。

工程的出现

用工程学的方法进行软件的开发与维护。对整个生产过程(从无到有)进行有效的过程管理。包括:时间、人员、方法、工具等等。

软件的生命周期:

1.寻找商机——项目 OR 产品

项目——有明确的需求提供方(甲方),为它量身定制软件。软件最终销售给甲方,并且甲方拥有该款软件的所有权。

产品——没有明确的需求提供方,由软件公司自己通过市场分析提出方向,同类产品对比,提出需求,自己销售。

2.可行性分析

2-1、法律法规、道德约束是否可行?

2-2、技术可行性

2-3、成本可行性

3.项目还有招投标

=======================

4.需求采集 和 需求分析

具有双向沟通能力,协调能力的人。这个过程里,主要任务就是对用户或市场的需求进行收集、归纳、对比、整理。保证功能和性能的准确要求。这一点将决定后期整个项目/产品的开发方向。

参与的人员:市场人员、产品经理、需求分析师、UI工程师、测试工程师

交付下一阶段的结果:《需求分析说明书》、界面原型

5.设计阶段——概要设计

实体分析:找出在项目里出现的实体,包括实体的属性、行为和关联关系;

技术选型:前端技术、后端技术、持久技术,包括框架、架构(C/S、B/S、分布式、微服务);

搭建项目:导包、Bean类设计、数据库设计。。。

参与人员:中央平台部、项目经理、或高级程序员(系统架构师)

交付下一阶段的结果:《概要设计说明书》、《数据字典》、项目工程搭建(GIT、Maven)

6.设计阶段——详细设计

分层设计是最主要的任务,把每一层的接口设计出来。

包括主要考虑:

6-1、抽象方法设计在哪个接口当中。

6-2、方法命名

6-3、参数和返回,在设计时一定要考量这个方法的层当中的职责。

数据库测试数据的录入:

一定要录入准确的、能说明业务场景的数据;一定要注意录入数据的关联关系的正确性。尽量涵盖各种业务情况。

参与者:项目组长

7.开发阶段

遵守开发流程,遵从设计、熟悉各种开发工具和框架(可能公司有自己的封装的框架)。

开发过程中,程序员还要和测试人员配合完成“单元测试”/“回归测试”、“集成测试”。

参与者:程序员、测试人员、文档管理人员。

8.测试阶段

功能性覆盖测试,压力测试,用户环境测试等等

9.部署与维护

软件生命周期模型

在整个软件开发过程当中,所有的这个步骤其实并不是第一个阶段完成后才能进入第二阶段的,那是一种理想情况。

瀑布模型;喷泉模型;

2、UML——统一建模语言

它是IBM公司提出的,用来从各个角度分析和描述一个项目,所使用的图形化语言。

UML的图例非常多:流程图、用例图、类图、ER图、泳道图...................

流程图:

描述一个业务流程的执行顺序。

用例图:

根据用户的身份,能够在本系统当中操作什么样的功能以及这些功能的前后关系。

类图:

  • 描述实体类之间的属性、行为和它们之间的关联关系。
  • 对于类图来说,通常是设计人员绘制,开发人员查看。设计人员通过专用的绘图工具,以拖拉填的形式形成类图,有些工具可以根据类图自动生成实体Bean的代码(甚至还可以选择编程语言)。比如:EA、RationalRose都可以。
  • 在面向对象里有些概念和我们在语法当中学到的有些概念有一些区别,这种区别就是纯理论和把理论付诸于编程语言设计所造成的差异性。1、继承不是继承,而是泛化关系;2、关联关系包含三种:依赖、聚合和组合。3、依赖就是use-a;聚合组合在Java语言里都是has-a,但是在面向对象思想里是不同的,前者叫has-a、后者叫contains-a;组合关系又叫做强聚合。
  • 在URL设计图里,掌握好:1、箭头的方向:实现和泛化关系的箭头是指向接口或者父类;关联关系的箭头是指向被关联对象的(注意:不是菱形或空心菱形);2、在关联关系的连线上方可以书写“几对几”;一方书写“1”,多方书写“0。。。*”或“n”;3、一根线代表单向关联,双项关联要画两根线。

3、设计的原则

软件设计的标准没有对错之分,满足软件的功能需求的设计就是对的设计。否则就是错的设计。但是,同样都能够满足软件功能的设计,又有优劣高低之分。这里的优劣高低的考量的方向。

1、可读性

在软件工程的立场上很重要的,不要使用生僻的算法。这里面包含内容很多,命名规范也好/书写注释的要求也好/固定统一的开发流程

2、可复用性

任何一个新的技术或者框架的提出,本质上只有一个目的:那就是分离与复用。所以在设计的时候需要考虑这个单元模块是不是能够被其它的模块复用,甚至能不能被以后的某个新的需求复用。

3、可扩展性

软件需求是会不断发生变化的,任何一个软件如果不考虑场外因素的话,都可以让任何一个公司做一辈子。项目的一期永远是为了后面的升级在做基础搭建,后期的工作量依赖于前期的设计是否有足够的冗余和复用。

4、可维护性

能够让维护人员快速的满足使用者的需求进行简单的修改处理,或者让后续接手的程序员能够在基于组件的设计上,哪个零件坏了换哪个,而不是牵一发动全身。

高内聚、低耦合

内聚度:

表示一个应用程序的单一单元所负责的任务数量和多样性。

理想状态下,一个代码单元负责一个内聚的任务,也就是说一个任务可以看作是一个逻辑单元,一个方法应该实现一个逻辑操作,一个类应该代表一种类型的实体。——这就是我们所谓的“高内聚”。让每个任务尽量集中在自己的模块当中。

耦合度:

表示单元模块之间的关联关系。松耦合就是建议这种关系应该简单且易替换。

软件工程当中,人们一说到判断一个设计的优良标准是什么?固定回答就是:高内聚、低耦合。

在实践操作里如何去把握高内聚低耦合?依赖的就是七个指导原则——设计原则。

设计原则

1、单一职责原则(四星):

任何一个模块(不仅仅是类或方法)都应该只负责一个职责,类的职责要单一。任何一个模块都不应该拥有在它职责之外去改变它的理由。

2、开闭原则(五星):

软件系统应该对于扩展新的功能是开放的,但是对修改已有的实现是关闭的。

它是所有原则里的核心原则,其它的原则都是为了在某一方面去体现开闭,而提出来的具体应用原则。

3、里氏替换原则(四星):

这个原则是用来判断两个类之间有无继承关系的原则。

我们现在的判断标准很原始,直接用的is-a关系。这种判定方式很多时候是基于我们的生活经验,但是在一些特殊的场景当中生活经验也是会有问题的。里氏替换原则就是提出抛开生活经验,根据具体的使用场景来进行设计是否需要用到继承。

正确的判定标准是:凡是父类能够正常工作的地方,子类也能正常工作,不应该引入额外的错误。只有满足这个条件,我们才能判定这两个类能够做继承。

4、依赖倒转原则(五星):

这个原则在后面的架构模式,以及框架使用的时候使用非常多,且在面试时常常被问到,因此为五星。

描述:上层模块依赖于下层模块的抽象,不要依赖于下层模块的具体实现。

在三层架构的设计当中,充分体现了这个原则。我们在表现层的设计当中,需要用到业务层,但是我们绑定的是业务层的抽象设计(业务层接口);业务层需要用到持久层,我们在业务层绑定的是持久层的抽象设计(持久层接口),它们的实现类对象最终都是通过框架注入的形式去完成的。

这样做的好处,一旦实现的方法发生变化,我们只需要重新书写一个新的实现类,完成对上层的注入即可。而不需要修改上层绑定的数据类型。以此达到开闭原则的要求。

5、接口隔离原则(两星):

又叫做最小接口原则。在设计接口和接口行为时,不要设计大而全的接口,尽量设计小而精的接口。

隔离——不让上层接口的设计污染了下层的子接口或者实现类,让他们拥有了自己不需要的方法。

注意:不要简单的认为一个接口就只设计一个方法。如果多个方法具有在底层同时出现或同时不出现的特征,那么我们也可以把这些方法的抽象设计在一个接口当中。

6、组合/聚合的复用原则(四星):

该原则指出在系统设计里,如果两个类要发生绑定关系,那么尽量使用组合/聚合,少用甚至是不用继承关系。

1、为什么?不用继承?

继承关系是一种在硬代码当中绑定的关系,也就是说它是我们在源代码里使用关键字去完成的关联关系。一旦关联之后,是没有办法在运行期去把它解绑或者替换的。这说明一旦发生改动,就一定会去动源代码。这不满足开闭。

但是组合聚合,却可以通过父类(接口)的引用指向所有的实现类对象,不管这个实现类是之前的还是后期增加的。而且绑定关系是可以通过运行期的(反射技术)进行关联或者解除关联的。所以从灵活性上来说,它满足开闭的。

2、是不是绝对不用?

继承也有继承的好处,特别是在类的体系结构建立上,它能够带来更清楚的树形结构,当然在Java里,更多时候,我们会设计的都是接口,而不是父类。

7、迪米特法则(三星):

也叫做”最少知识原则“。一个模块应该在完成它的基本功能的保障前提下,尽可以少的去关联别的模块。

尽量减少引起自身改变的因素。

4、设计模式(Design pattern重点)23种

选讲的原则:1、你们用过或见过;2、后面阶段要用到的;3、面试常见常写。

模式:说白了就是套路

  • 我们在开发过程中遇到的很多问题,其实都不是单一或者是独立存在的。有很多问题都是在不同的项目系统里反复出现。那么这些反复出现的问题如果不进行归纳整理,那么无疑是一种损失。
  • 所以,针对很多在不同时期不同场景中反复出现的问题,我们通过几代程序员的总结归纳形成了统一的解决方案。那么以后再遇到同样的问题时,无需动脑自己重新想办法,自己直接使用这些先人的智慧即可。

当然学习这些模式是有一些基本的要点:

1、要清楚这个模式要解决什么问题的。它的背景、问题场景要清楚

2、很多模式都有不止一种解决方案,那么要清楚各种解决方案的优劣,在合适的时候选择使用合适的方案。

3、一定要去记忆模式的名字,这有利于我们对这个模式特点的理解,以及与其它程序员的交流。

4、很多模式采用的设计思想不仅仅体现在软件编程中,甚至也是生活中同类问题的解决方案,对于我们来说也有机会的话也可以站在更多的场景见到或者使用它们。

模式在软件工程里分为两类:

1、设计模式

设计模式是代码级别的,也就是针对某个具体的场景(要求)在代码级别实现的套路,它是微观的。

2、架构模式

架构模式是在项目工程的组织结构,内容搭建,设计方向这些宏观上的设计套路。

23种设计模式:

所有的设计模式,与平台无关、与编程语言无关

总共分为三个大类:

 1、创建模式——创建某种特殊的对象,或者用某种特殊的方式创建对象。

单例模式(Singleton模式):

问题域:设计一个类,这个类能且只能产生一个对象。

实现单例模式总共有五种方式:

五种方式的首要前提是构造方法必须私有化!!!!

第一种:饿汉单例

在使用类获取对象的时候,对象已经提前创建好了。

1、构造方法私有化(所有的单例模式都需要这一点。)
2、提供一个静态属性instance,让它指向了一个Singleton对象
3、getInstance()方法的必要性是没有的,完全可以把instance设置为公共。

为什么叫饿汉?

因为实例对象instance对象是在Singleton类加载期就会自动产生。
也就是说不管是否需要这个对象,只要一加载马上就生成,生怕没有可供使用的对象了。

优点:

由于对象是在加载期产生的,所以运行起来之后,无论在哪个线程里用到它,都是使用的同一对象,它是”线程安全“的,无需采用其它手段来保证安全性。由于没有锁机制,支持”高并发“的。

缺点:

  • 不支持”延迟加载“。
  • 由于只要加载这个类就会自动产生对象,无论我们是否需要,那么在这种方式我们叫做“预加载”。
  • 而在很多应用场景里,我们需要的是真正调用getInstance方法的时候,这个时候才表明我需要这个对象,那么这个时候才产生这个对象。这种方式叫做”延迟加载“。
public class Singleton {
    /*
    提供一个静态属性instance,让它指向了一个Singleton对象
    getInstance()方法的必要性是没有的,完全可以把instance设置为公共。
     */
    /*private static Singleton instance = new Singleton();

    public static Singleton getInstance(){
        return instance;
    }*/

    //饿汉单例是在获取对象前,对象已经提前准备好了一个。
    //这个对象只能是一个,所以定义静态成员变量记住。
    public static Singleton instance = new Singleton();

    //1、构造器私有!
    //如果不私有化,可以随意的创建对象,就不能实现单例了。
    private Singleton(){}
}

第二种:懒汉单例

  • 在真正需要该对象的时候,才去创建一个对象(延迟加载对象)。
  • 在类加载的时候不主动生成实例对象,只有在第一次执行到getInstance()方法的时候,这个时候真正需要这个对象了,那么new一个返还回去。在new好之后,为了保证单例,也不再重新new对象了。

优点:

支持”延迟加载“。

缺点:

  • 线程不安全,因为有可能第一次就有多个线程同时来调用这个getInstance()方法。
  • 为了解决线程安全性问题,那么我们需要给getInstance()方法加同步锁synchronized 。加了同步锁,线程安全了,但是不再支持高并发。
public class Singleton {
    /*
    定义一个静态的成员变量负责存储一个对象。只加载一次,只有一份。
     */
    private static Singleton instance;

    /*
    提供一个getInseance方法,返回一个Singleton对象。
     */
    //为了解决线程安全问题,可以加同步锁synchronized
    public synchronized static Singleton getInstance(){
        if (instance == null){
            //第一次来拿对象,此时需要创建对象
            instance = new Singleton();
        }
        //非null的话,就不创建对象了。
        return instance;
    }
    //1、构造器私有!
    private Singleton(){}
}

第三种:单例模式的双锁机制

  • 不是指有两把锁,而是在同步块的内外分别做了一次非空判断。
  • 外层的非空判断,其作用是当Instance对象产生以后,无论多大的并发量都通过这个判断绕开了这个同步块,从而让这个设计有了支持“高并发”的效果。
  • 而内层的非空判断,作用是如果在对象还没有产生的时候,多个线程都进入了外层判断,可以让他们不会重复产生Instance对象,从而让这个设计有了线程安全的效果。

优点:

线程安全;延迟加载;效率较高。支持高并发。

缺点:

Java在JDK1.5之后才支持,所以导致长期依赖Java的底层设计没有使用这种方式,反而让另一种方式大兴其道 --- 静态内部类

public class Singleton {
    private static volatile Singleton instance;//volatile关键字!!!
    /*当一个线程修改了它的值,让其它线程能够马上看到这个值的变化。如果这里不加这个关键字
    可能会出现,一个线程进入锁之后,产生了一个Singleton对象,但是另一个线程进入锁,此时
    这个线程并没有看到新产生的对象,又产生了一个Singleton对象。
    */
    public static Singleton getInstance(){
        //外层的非null判断,使其支持高并发
        if (instance == null){
            synchronized (Singleton.class) {
                //内层的非null判断,使其线程安全
                if (instance ==null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    //1、构造器私有!
    private Singleton(){}
}

第四种:静态内部类

支持延迟加载,支持高并发,支持线程安全

public class Singleton{
    private Singleton() {}

    private static class SingletonHolder{
        public static Singleton instance = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}
//第二种写法
class SingletonHolder{
    private static class Singleton{
        public static Singleton instance = new Singleton();
    }

    public static Singleton getInstance(){
        return Singleton.instance;
    }
}

第五种:枚举

enum Singleton{
    instance;
}

有一位博主的枚举写的很详细:

单例模式(五种实现)_刚入门的Rain的博客-CSDN博客_写一个简单的单例模式单例模式一、什么是单例模式单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。单例的实现主要是通过以下两个步骤:将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对https://blog.csdn.net/weixin_45776349/article/details/119752757?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-2-119752757.142%5Ev50%5Econtrol,201%5Ev3%5Eadd_ask&spm=1018.2226.3001.4187

工厂模式(Factory)

这篇文章也有写工厂模式:

JavaSE基础笔记——XML、XML解析、设计模式_小曹爱编程!的博客-CSDN博客XML:概述、创建、语法规则、XML文档约束方式-DTD约束与schema约束(了解)。XML解析技术:概述、Dom4j解析XML文件、案例实战。XML检索技术:Xpath。设计模式:工厂模式与装饰模式https://blog.csdn.net/weixin_62993347/article/details/126571876?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166428571716782428655006%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=166428571716782428655006&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-126571876-null-null.article_score_rank_blog&utm_term=%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450

问题域:

把产生对象的动作和使用对象的两个动作进行职责分离。设计专门的工厂类,来产生产品对象。然后交给使用者使用产品对象。

当我们需要用到某个对象时,总是自己先new,然后再点。

只要具备这种分离思想的就是工厂模式。工厂模式如果进一步为了丰富度进行抽象设计,又分为三种情况,简单工厂模式,工厂方法模式,抽象工厂模式。

1、简单工厂模式

同一个工厂可以根据用户需求产生不同的产品。

定义一个创建对象的类,这个类来封装实例化对象的行为。即工厂类。

举例:

代码如下:

KFC工厂类。根据用户需求一共产生三种类型的KFC产品:汉堡、薯条、可乐。产品对象继承KFC产品的抽象类(KFCproduct)。通过工厂类(KFCFactory )实例化这三种类型的对象。

/*
KFC产品
 */
public abstract class KFCproduct {
    private String productname;//产品的描述(产品名称)

    public String getProductname() {
        return productname;
    }

    public void setProductname(String productname) {
        this.productname = productname;
    }

    //买产品
    public abstract void buyProduct();
}
/*
KFC工厂
 */
public class KFCFactory {
    public static KFCproduct order(String commond){
        KFCproduct kfcproduct = null;
        switch (commond){
            case "汉堡":
                kfcproduct = new Hamburger();
                kfcproduct.setProductname("香辣鸡腿堡");
                break;
            case "薯条":
                kfcproduct = new Chips();
                kfcproduct.setProductname("大薯条");
                break;
            case "可乐":
                kfcproduct = new Cola();
                kfcproduct.setProductname("大可乐");
                break;
            default:
                kfcproduct = null;
        }
        return kfcproduct;
    }
}
/*
汉堡
 */
public class Hamburger extends KFCproduct{
    @Override
    public void buyProduct() {
        System.out.println("您购买了一个" + getProductname());
    }
}
/*
薯条
 */
public class Chips extends KFCproduct{
    @Override
    public void buyProduct() {
        System.out.println("您购买了一个" + getProductname());
    }
}
/*
可乐
 */
public class Cola extends KFCproduct{
    @Override
    public void buyProduct() {
        System.out.println("您购买了一个" + getProductname());
    }
}
public class TestMain {
    public static void main(String[] args) {
        KFCproduct hamburger = KFCFactory.order("汉堡");
        hamburger.buyProduct();

        KFCproduct chips = KFCFactory.order("薯条");
        chips.buyProduct();

        KFCproduct cola = KFCFactory.order("可乐");
        cola.buyProduct();
    }
}

简单工厂模式有一个问题就是:

类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了开闭原则,所以,从设计角度考虑,有一定的问题,如何解决?我们可以定义一个创建对象的抽象方法并创建多个不同的工厂类实现该抽象方法,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。这种方法也就是我们接下来要说的工厂方法模式。

2、工厂方法模式

同一个产品,不同的工厂生产的过程或效果可以有不同。

举例:

汉堡这一个产品,有两个工厂再做,抽取工厂为接口,一个是KFC实现类,一个是麦当劳实现类,然后重写工厂接口里的方法。如果我们添加一个新的工厂,如果是简单工厂模式,我们需要去修改工厂代码。而使用工厂方法模式,可以直接创建一个新的工厂的实现类就可以了。

public class Hamburger {
    private String content;//产品对象的名字

    public Hamburger(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Hamburger{" +
                "content='" + content + '\'' +
                '}';
    }
}
/*
由于工厂在变化,所以抽取工厂为接口
 */
public interface IFactory {
    //创建汉堡的方法
    public Hamburger createHamburger();
}
/*
KFC工厂
 */
public class KFCFactory implements IFactory{
//不同的实现,KFC工厂制作的是鸡肉汉堡
    @Override
    public Hamburger createHamburger() {
        return new Hamburger("鸡肉");
    }
}
/*
麦当劳工厂
 */
public class MCDFactory implements IFactory{
//不同的实现,KFC工厂制作的是牛肉汉堡
    @Override
    public Hamburger createHamburger() {
        return new Hamburger("牛肉");
    }
}

其实这个模式的好处就是,如果你现在想增加拓展一个新的功能,只需做一个工厂实现类就OK了,无需去改动现成的代码。这样做,拓展性较好!

3、抽象工厂模式

不同的产品在不同的工厂生产,可以有过程和效果的差异性。

设计一个工厂的抽象类,再设计一个产品的抽象类,创建不同的工厂实现类和产品实现类实现这两个抽象类。

//抽象工厂类
public abstract class Factory {
    public abstract IProduct order(String commond);
}
/*
    产品进行抽象
 */
public interface IProduct {
}
public class ShaXianFactory extends Factory{
    @Override
    public IProduct order(String commond) {
        if(commond.equals("面条")){
            System.out.println("沙县特色制作过程");
            return new MianTiao();
        }else if(commond.equals("水饺")){
            System.out.println("沙县特色制作过程");
            return new ShuiJiao();
        }
        return null;
    }
}
public class ZhongFactory extends Factory{
    @Override
    public IProduct order(String commond) {
        if(commond.equals("面条")) {
            System.out.println("对不起,没有面条");
        }else if(commond.equals("水饺")){
            System.out.println("水饺使用调料调味");
            return new ShuiJiao();
        }
        return null;
    }
}
public class ShuiJiao implements IProduct{
}
public class MianTiao implements IProduct{
}

综述:

工厂模式的核心就是生产者和消费者的分离,其他的变化无非是根据场景的丰富度进行进一步的抽象设计。

在做抽象设计的时候,最基本的原则就是谁变化抽象谁。这个原则不仅仅是工厂模式,应该说所有的设计模式都可能发生,都要依赖这个原则。

面试地位:

面试里考的不多,不是因为不重要,而是这个模式太常见了。默认都应该知道。

原型模式(prototype)

核心思想问题域:

根据一个已有对象,产生另一个新的对象,但内容跟之前一模一样的对象。也就是克隆clone。

由于在Java当中的根类Object里,已经有一个现成的方法叫做clone,该方法实现了克隆的效果,所以Java当中的原型模式无需我们做额外设计,只需要使用API就可以了。

1、浅克隆

  • Object的clone方法被调用后,它的效果是只对当前对象进行克隆,然后把原对象的属性值依次拷贝到克隆对象的属性里。
  • 这种机制导致了,如果原对象的属性里有关联对象,那么克隆对象里并没有产生新的关联对象,只是和原对象一起都指向同一个关联对象。
  • 那么我们就认为clone方法只克隆了当前对象这一层,更深层次的关联对象(还可能关联对象里又有关联对象),都没有被克隆,所以叫做”浅克隆“。

例子:

有一个学生类,当学生没有关联对象时。

/*
clone方法想要被支持,必修让调用该方法的类实现一个标识性接口——Cloneable
它表示通知JVM允许对该类的对象做clone操作
 */
public class Student implements Cloneable{
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public classRoom getTheclass() {
        return theclass;
    }

    public void setTheclass(classRoom theclass) {
        this.theclass = theclass;
    }
    
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age + "}";
    }
    
    //浅克隆
    /*
        克隆方法在Object当中是从Object里继承而来的,
        但是该方法的访问修饰符是Protected——受保护的
        这意味着这个克隆方法只能在该子类的内部被访问到,在该子类的外部访问不到。
        所以,通过重写提升它的访问修饰符。而实现部分,继续使用父类中的实现代码,
        使用super.的方式调用父类的实现。
         */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class TestPrototype {
    public static void main(String[] args) throws Exception {
        Student zhangsan = new Student("张三",20);
        //不使用克隆
        Student student = new Student(zhangsan.getName(),zhangsan.getAge());
        System.out.println(zhangsan == student);//false
        System.out.println(student);//Student{name='张三', age=20}

        System.out.println("==================");
        //浅克隆
        Student zhangsan1 = (Student) zhangsan.clone();
        System.out.println(zhangsan == zhangsan1);//false
        System.out.println(zhangsan1);//Student{name='张三', age=20}
    }
}

而如果给学生类加一个关联对象班级信息的话,会导致克隆对象里并没有产生新的关联对象,只是和原对象一起都指向同一个关联对象。

public class classRoom {
    private String className;
    private int stuNum;

    public classRoom() {
    }

    public classRoom(String className, int stuNum) {
        this.className = className;
        this.stuNum = stuNum;
    }

    @Override
    public String toString() {
        return "classRoom{" +
                "className='" + className + '\'' +
                ", stuNum=" + stuNum +
                '}';
    }
}
/*
clone方法想要被支持,必修让调用该方法的类实现一个标识性接口——Cloneable
它表示通知JVM允许对该类的对象做clone操作
 */
public class Student implements Cloneable{
    private String name;
    private int age;
    private classRoom theclass;//班级

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public classRoom getTheclass() {
        return theclass;
    }

    public void setTheclass(classRoom theclass) {
        this.theclass = theclass;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", theclass=" + theclass +
                '}';
    }

    //浅克隆
    /*
        克隆方法在Object当中是从Object里继承而来的,
        但是该方法的访问修饰符是Protected——受保护的
        这意味着这个克隆方法只能在该子类的内部被访问到,在该子类的外部访问不到。
        所以,通过重写提升它的访问修饰符。而实现部分,继续使用父类中的实现代码,
        使用super.的方式调用父类的实现。
         */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class TestPrototype {
    public static void main(String[] args) throws Exception {
        Student zhangsan = new Student("张三",20);
        zhangsan.setTheclass(new classRoom("J190",20));

        Student zhangsan1 = (Student) zhangsan.clone();
        System.out.println(zhangsan == zhangsan1);//false
        System.out.println(zhangsan1);//Student{name='张三', age=20, theclass=classRoom{className='J190', stuNum=20}}
        System.out.println(zhangsan1.getTheclass() == zhangsan.getTheclass());//true
        //说明其关联对象指向同一对象
    }

解决办法就是使用深克隆!

2、深克隆

实现手段1:

让每一个关联对象,自己去实现Cloneable接口,自己去重写clone方法。然后在我们克隆整体外部对象的时候,我们添加代码,自己去克隆关联对象,并做出拼接动作。

这种手段虽然可行,但是对于结构复杂,结构层次深的对象,构建和书写起来多是非常麻烦的事情,所以,一般不采用这种手段来实现深克隆。

代码:

让班级对象,实现Cloneable接口,自己去重写clone方法。

public class classRoom implements Cloneable{
    private String className;
    private int stuNum;

    public classRoom() {
    }

    public classRoom(String className, int stuNum) {
        this.className = className;
        this.stuNum = stuNum;
    }

    @Override
    public String toString() {
        return "classRoom{" +
                "className='" + className + '\'' +
                ", stuNum=" + stuNum +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

更改学生类里面的克隆方法,关联班级对象

@Override
    public Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.setTheclass((classRoom) this.theclass.clone());
        return student;
    }
public class TestPrototype {
    public static void main(String[] args) throws Exception {
        Student zhangsan = new Student("张三",20);
        zhangsan.setTheclass(new classRoom("J190",20));

        //深克隆实现方式1
        System.out.println("==================");
        Student zhangsan1 = (Student) zhangsan.clone();
        System.out.println(zhangsan == zhangsan1);//false
        System.out.println(zhangsan1);//Student{name='张三', age=20}
        System.out.println(zhangsan1.getTheclass() == zhangsan.getTheclass());//false
    }
}

实现手段2:

利用序列化和反序列化技术实现深克隆。思想是:把原型对象先用序列化转换为二进制流,再把这个二进制流用反序列化产生新的对象。这个新的对象就是我们要的克隆对象。

这里要两个考量:

1、所有参与序列化的类型都应该实现Serializable接口

2、对象的序列化和反序列化与文件无关,这个操作使用ByteArray替代文件。

这样深克隆的代码就被固定下来了。

这是序列化和反序列化实现深克隆的固定代码

public Object deepClone(){
        Object obj = null;
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);){
            //把当前对象序列化出去
            oos.writeObject(this);
            //反序列化出去
            try(ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
                ObjectInputStream is = new ObjectInputStream(byteArrayInputStream);){
                obj = is.readObject();
            }catch (Exception e){
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }

代码:

让学生与班级都实现Serializable接口,然后在学生类中克隆方法代替为上面的深克隆实例化示例代码。

public class TestPrototype {
    public static void main(String[] args) throws Exception {
        Student zhangsan = new Student("张三",20);
        zhangsan.setTheclass(new classRoom("J190",20));

        //深克隆实现方式2
        System.out.println("==================");
        Student zhangsan1 = (Student) zhangsan.deepClone();
        System.out.println(zhangsan == zhangsan1);//false
        System.out.println(zhangsan1);//Student{name='张三', age=20}
        System.out.println(zhangsan1.getTheclass() == zhangsan.getTheclass());//false
    }
}

面试地位:

一般不会手工书写代码,主要出现方式是问深浅克隆的区别。  

使用clone方法。使用clone方法必须满足:

  1. 实现Cloneable接口
  2. 使用public访问修饰符重新定义clone方法。

浅克隆与深克隆的区别

  • 1、浅克隆:对当前对象进行克隆,并克隆该对象所包含的8种基本数据类型和String类型属性(拷贝一份该对象并重新分配内存,即产生了新的对象);但如果被克隆的对象中包含除8中数据类型和String类型外的其他类型的属性,浅克隆并不会克隆这些属性(即不会为这些属性分配内存,而是引用原来对象中的属性)
  • 2、深克隆:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

浅克隆与深克隆的特点

  • 1、被克隆的对象的类应实现 Cloneable 接口,并重写 clone() 方法
  • 2、浅克隆中由于除8中数据类型和String类型外的其他类型的属性不会被克隆,因此当通过新对象对这些属性进行修改时,原对象的属性也会同时改变。而深克隆则已经对这些属性重新分配内存,所以当通过新对象对这些属性进行修改时,原对象的属性不会改变。

建造者模式(Builder)

问题域:

在创建一个复杂对象(该对象内部包含了很多部件)时,我们把整个创建的过程分离为具体部件的创建者和协调创建指令的指挥者。

在整个Builder模式中,有三个角色:复杂对象、创建者对象、指挥者对象(通常指挥者是由外部调用者充当的)。

复杂对象拥有很多部件,然后只能通过构建者来创建; 构建者自身包含了创建每个部件的方法,以及每个部件作为自己的属性。然后再通过一个独有的build方法(这个方法可以看成是Builder模式的标志语法)去创建复杂对象,把自己内部的部件交给复杂对象。另外,创建者所有的创建部件方法都会返回自己本身,这样外部调用可以采用连续调用的语法实现。

第一种演示:分开的Product对象和ProductBuilder对象;

/*
Product的就是用来模拟一个复杂对象的
里面四个属性ABCD,用来模拟四个部件。
对于一个Product对象来说,4个部件构建谁?不构建谁?先构建谁?后构建谁?
都是由外部的使用场景所决定。
*/
public class Product {
    private int partA;
    private String partB;
    private double partC;
    private boolean partD;

    /*
    产品只提供一个构造,是通过Builder来构造,这样也让外部只能通过Builder类创建对象。
     */
    public Product(ProductBuilder builder) {
        this.partA = builder.getPartA();
        this.partB = builder.getPartB();
        this.partC = builder.getPartC();
        this.partD = builder.isPartD();
    }

    //不重要,只是为了看最后的结果
    @Override
    public String toString() {
        return "Product{" +
                "partA=" + partA +
                ", partB='" + partB + '\'' +
                ", partC=" + partC +
                ", partD=" + partD +
                '}';
    }
}
/*
创建者类,它的任务:
1、要单独创建部件ABCD、然后把部件交给Product对象
    所以它要有这些部件作为属性,以及创建这些部件的独立行为
2、创建者在设计当中每个独立部件的创建行为最大的设计特征
就是要返回当前的创建者对象
 */
public class ProductBuilder {
    private int partA;
    private String partB;
    private double partC;
    private boolean partD;

    //创建某个部件的独立行为
    public ProductBuilder createPartA(int a){
        this.partA = a;
        return this;
    }

    public ProductBuilder createPartB(String b){
        this.partB = b;
        return this;
    }

    public ProductBuilder createPartC(double c){
        this.partC = c;
        return this;
    }

    public ProductBuilder createPartD(boolean d){
        this.partD = d;
        return this;
    }

    public int getPartA() {
        return partA;
    }

    public String getPartB() {
        return partB;
    }

    public double getPartC() {
        return partC;
    }

    public boolean isPartD() {
        return partD;
    }

    //返回最终产品的方法
    public Product build(){
        return new Product(this);
    }
}
public class TestDirector {
    public static void main(String[] args) {
        /*
        这里的TestDirector就是在充当指挥者,它的任务只是找到一个民工Builder,然后按需要指挥员工创建需要的部件以及创建的顺序,最后让民工返回最终的结果。
         */
        Product product = new ProductBuilder().createPartA(18).createPartB("张三").createPartC(25.5).build();
        System.out.println(product);

        //静态内部类的方法
        Product2 product2 = new Product2.Builder().createPartB("李四").createPartA(20).build();
        System.out.println(product2);
    }
}

第二种演示:在Product对象当中设计ProductBuilder静态内部类

/*
    外部类是我们的产品对象
 */
public class Product2 {
    private int partA;
    private String partB;
    private double partC;
    private boolean partD;
    public Product2() {
    }
    public static class Builder{
        private int partA;
        private String partB;
        private double partC;
        private boolean partD;

        //创建某个部件的独立行为
        public Builder createPartA(int a){
            this.partA = a;
            return this;
        }

        public Builder createPartB(String b){
            this.partB = b;
            return this;
        }

        public Builder createPartC(double c){
            this.partC = c;
            return this;
        }

        public Builder createPartD(boolean d){
            this.partD = d;
            return this;
        }

        public Product2 build(){
            Product2 product2 =  new Product2();
            product2.partA = this.partA;
            product2.partB = this.partB;
            product2.partC = this.partC;
            product2.partD = this.partD;
            return product2;
        }
    }

    @Override
    public String toString() {
        return "Product{" +
                "partA=" + partA +
                ", partB='" + partB + '\'' +
                ", partC=" + partC +
                ", partD=" + partD +
                '}';
    }
}

面试地位:

地位不高,最多就是在选择题里出现,但是Builder模式是一个在创建复杂对象的时候,非常有效能够减少我们的代码量,同时极具语法特征的设计模式。所以,以后会在别的开发框架或平台看到它,出现几率不低。

 2、结构模式——类与类的各种组合关系,从而能够达到最好的高内聚低耦合的效果。

结构模式是一种在类与类之间设计的关联关系的模式,让这些类通过继承或组合形成更好的类层次结构,从而能适应某些特殊的应用场景,带来更好的使用效果和体验。  

外观模式(Facade)

  • 作为调用者,有时候我们在调用一个复杂系统的时候,需要分步调用里面的每一个子系统,把它们整合起来才能够完成一个任务。
  • 在这种情况下,外部调用者必须对这个复杂系统的每个子系统都很熟悉。这样的话,外部调用者的代码就会和这个复杂系统高度耦合。
  • 那么更合适的一种做法:由这个复杂系统提供一个对外的统一接口(这里的接口不是interface数据类型,而是指对外的一个对接的接口)。外部调用者之需要调用这个接口,然后这个接口自己内部去完成各个子系统的调用。

简单来说,该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。这个模式中,设计到3个角色。

代码

子系统:实现了子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。(实现具体功能)

public class SubSystem1 {
    public void function11(){}
    public void function12(){}
}
public class SubSystem2 {
    public void function21(){}
    public void function22(){}
    public void function23(){}
}
public class SubSystem3 {
    public void function31(){}
}

门面角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。(客户调用,同时自身调用子系统功能)

/*
    对外统一的外观类
 */
public class FacadeSystem {
    private static SubSystem1 s1 = new SubSystem1();
    private static SubSystem2 s2 = new SubSystem2();
    private static SubSystem3 s3 = new SubSystem3();

    public static void mission1(){
        s1.function12();
        s2.function22();
        s2.function23();
    }

    public static void mission2(){
        s1.function11();
        s2.function21();
        s3.function31();;
    }

}

客户角色:通过调用Facede来完成要实现的功能(调用门面角色)

public class TestFacade {

    public static void main(String[] args) {
        //任务一:
        FacadeSystem.mission1();
        //任务二:
        FacadeSystem.mission2();
    }
}

面试地位:

几乎为0 

桥梁模式(Bridge)

问题域:

在很多情况下,我们需求的变化度是多维度的,如果我们试图让一个类去控制多维度的变化,那么会给我们带来很大的麻烦。首当其冲就是,任何一个维度的变化,或者是维度与维度的组合变化,都会让我们去书写新的实现。从而造成一种“类爆炸“的效果(类越来越多)。

解决的方案是什么呢?

不要让一个类承担多维度的变化,而是让每个类只承担一个维度的变化,然后让多个类进行协同来完成任务。这样每个类只承担一个维度的变化。

协同的时候,用组合的关系,在类图上形成一个”桥“。

面试地位:

几率很小,最多是在选择题中作为结构模式的一个选项出现,实际场景当中经常看到,也经常设计出来。设计模式-桥梁模式_逍遥壮士的博客-CSDN博客上文(适配器模式):https://blog.csdn.net/qq_16498553/article/details/106484298背景日常生活中,每家每户都是有电视机的,但是相同的电脑机可能不同的型号就会出现只能专机专用遥控器,这样一来家里要是有几个房间,可否统一成一个遥控器来控制?桥梁模式是什么?桥梁模式是结构型模式的一种。将实现和抽象进行解耦,起到一个低偶高内聚,使抽象和实现都可以独立的变化。桥梁模式可以干什么?主要是解决继承方面存在缺陷而设计,一个类想要拥有另外一.https://blog.csdn.net/qq_16498553/article/details/106486273?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166453319216800186533146%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166453319216800186533146&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-106486273-null-null.142%5Ev51%5Econtrol,201%5Ev3%5Eadd_ask&utm_term=%E6%A1%A5%E6%A2%81%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4187

 装饰器模式(Decorator)

 这篇文章也有写装饰模式:

JavaSE基础笔记——XML、XML解析、设计模式_小曹爱编程!的博客-CSDN博客XML:概述、创建、语法规则、XML文档约束方式-DTD约束与schema约束(了解)。XML解析技术:概述、Dom4j解析XML文件、案例实战。XML检索技术:Xpath。设计模式:工厂模式与装饰模式https://blog.csdn.net/weixin_62993347/article/details/126571876?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166428571716782428655006%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=166428571716782428655006&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-126571876-null-null.article_score_rank_blog&utm_term=%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450

动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。

代码举例子:咖啡的订单

被装饰的对象和装饰者都继承同一个超类(咖啡的配料和不同的咖啡都继承同一个咖啡类)

/*
咖啡类
 */
public abstract class Coffee {
    private String description;//描述行为

    public Coffee(String description){
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public abstract double cost();//返回咖啡的价格
}

不同的咖啡都继承咖啡类

public class BlueMoutainCoffee extends Coffee{

    public BlueMoutainCoffee(){
        super("蓝山咖啡");
    }

    @Override
    public double cost() {
        return 25.0;
    }
}
public class Cappuccino extends Coffee{

    public Cappuccino(){
        super("卡布基诺");
    }

    @Override
    public double cost() {
        return 22.5;
    }
}

配料类的父类也继承咖啡类

/*
配料类:
设计要点:
1、让配料继承了咖啡,因为场景里有一句话:配料不能单独存在的。
这里的配料其实不是奶、糖,而是奶咖啡、糖咖啡才能存在。
2、在配料里组合了一个咖啡对象,因为场景里配料是修饰咖啡,但是修饰的是
哪种咖啡是可以任意组合的。
 */
public abstract class Peiliao extends Coffee{
    private  Coffee coffee;

    public Peiliao(String description, Coffee coffee){
        super(description);
        this.coffee = coffee;
    }

    public Peiliao(String description) {
        super(description);
    }

    public Coffee getCoffee() {
        return coffee;
    }
}

不同配料类,继承配料父类

public class Love extends Peiliao{

    public Love(Coffee coffee){
        super("爱情",coffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + "+" + this.getCoffee().getDescription();
    }

    @Override
    public double cost() {
        return 0.0 + this.getCoffee().cost();//爱情配料不要钱
    }
}
public class Milk extends Peiliao{

    public Milk(Coffee coffee){
        super("牛奶",coffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + "+" + this.getCoffee().getDescription();
    }

    @Override
    public double cost() {
        return 3 + this.getCoffee().cost();//牛奶配料三块钱
    }
}
public class TestDecorator {
    public static void main(String[] args) {

        Coffee myCoffee = new Love(new Milk(new Milk(new Cappuccino())));

        System.out.println(myCoffee.getDescription());//爱情+牛奶+牛奶+卡布基诺
        System.out.println(myCoffee.cost());//28.5
    }
}

装饰者和被装饰者之间必须是一样的类型,也就是要有共同的超类。在这里应用继承并不是实现方法的复制,而是实现类型的匹配。因为装饰者和被装饰者是同一个类型,因此装饰者可以取代被装饰者,这样就使被装饰者拥有了装饰者独有的行为。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者增加新的行为。如果是用继承,每当需要增加新的行为时,就要修改原程序了。

面试地位:

出现几率不高,通常就是问你在使用Java的过程在哪些地方见过哪些设计模式?

IO流/缓冲流里就是使用的装饰器模式!

适配器模式(Adapter)

适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。

主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

问题域:

适配器模式是指现有系统的接口和被调用系统的接口因为设计的原因不匹配,没有办法直接调用到,所以在它们之间设计一个适配器去匹配两者,协调它们的接口。

GUI里,曾经在事件处理模型里见过适配器。在JDK的事件处理里。由于监听器是根据不同任务进行划分的,所以每个监听器都只拥有自己的独立行为,但是如果我们需要在一个事件源上使用两个监听器的方法,那么我们就要用JDK里的提供监听器Adaptor,而不是单独使用两个监听器接口。

面试地位:

最近五年以上,几乎没见过适配器的题目。以前最爱问的题目:有两个类A和B,A有a方法,B有b方法,如何让一个类即具有A,又具有B。这时候,就需要有一个中间的"适配器",既有A的a方法,又有B的b方法:

//ClassA 有methodA方法
public class ClassA {
    public void methodA(){
        System.out.println("a");
    }
}
//ClassB 有methodB方法
public class ClassB {
    public void methodB(){
        System.out.println("b");
    }
}
/*
    适配器实现方式一:继承一个,组合一个
 */
public class ClassAB extends ClassA{
    private ClassB classB = new ClassB();
    /*
        定一个与ClassB中的methodB完全一样的方法声明,
        然后在内部调用ClassB的实现
     */
    public void methodB(){
        classB.methodB();
    }
}
/*
适配器实现方式二:组合两个
 */
public class ClassAB1 {
    private ClassA classA = new ClassA();
    private ClassB classB = new ClassB();

    public void methodA(){
        classA.methodA();
    }

    public void methodB(){
        classB.methodB();
    }
}
public class TestAdaptor {
    public static void main(String[] args) {
        /*
            我们需要一个类既有methodA,又有methodB
         */
        ClassAB classAB = new ClassAB();
        classAB.methodA();
        classAB.methodB();

        ClassAB1 classAB1 = new ClassAB1();
        classAB1.methodA();
        classAB1.methodB();
    }
}

代理模式(Proxy)

定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

由于某种原因导致,客户程序没有办法(或不愿意)调用到真正的目标对象的目标行为,那么,我们就增加一个对于这个目标对象的代理对象,让客户程序访问调用这个代理对象,再让这个代理对象去访问操作目标对象。当然,代理对象再代理的过程当中,可以不是仅仅指调用目标对象的目标方法,还能增加自己的额外行为。

1、静态代理

这里的静态不是static,而是指代的编译期。

在编译期,我们就创建好了目标类、代理类,然后客户程序调用代理类,代理类调用目标类。

代码举例:买房

目标类接口

/*
由于目标对象和代理对象,都拥有相同的行为,只是行为的实现不一样。
所以,我们进行抽取,设计出它们共有的接口或父类。
 */
public interface BuyHouse {
    void buyHosue();
}

实现目标类接口(目标类)

//目标类(Target)
public class BuyHouseImpl implements BuyHouse {
       @Override
       public void buyHosue() {
              System.out.println("我要买房");
       }
}

创建代理类

/*
代理类(Proxy)
 */
public class BuyHouseProxy implements BuyHouse {
       private BuyHouse buyHouse;
       public BuyHouseProxy(final BuyHouse buyHouse) {
              this.buyHouse = buyHouse;
       }
       @Override
       public void buyHosue() {
              System.out.println("买房前准备");
              buyHouse.buyHosue();
              System.out.println("买房后装修");
       }
}

总结:

优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

缺点: 代理对象与目标对象要实现相同的接口,我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。

2、动态代理

JavaSE基础笔记——单元测试(Junit框架)、反射、注解、动态代理_小曹爱编程!的博客-CSDN博客单元测试:概述、快速入门、常用注解反射:概述、获取类对象、获取构造器对象、获取成员变量对象、获取方法对象、反射的作用-绕过编译阶段为集合添加数据、通过框架的底层原理。注解:概述、自定义注解、元注解、注解解析、注解的应用场景一:Junit框架。动态代理(重点):概述、快速入门、应用案例:做性能分析、代理的好处。...扩展知识:数组的可变参数的语法;Transient关键字的扩展https://blog.csdn.net/weixin_62993347/article/details/126571839?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166453750116782425141691%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=166453750116782425141691&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-126571839-null-null.article_score_rank_blog&utm_term=%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450这里的动态就是运行期。

在编译期,我们只创建目标类,程序运行起来之后,动态产生这个目标类的代理对象。然后客户程序调用目标对象,但是底层是调用的代理类对象。

如何实现?

动态代理有两种实现的API,一个是JDK当中自带的原生API;这个原生API的设计并不完善,至少在动态上的体现并不是完全做到了隐藏。另一个是来自于第三方的包——cglib.jar。这个包当中的API就设计的比较好。后面的框架在底层如果要实现动态代理,都会使用的API。所以这个jar包之后必导入。

本课程只讲JDK的API,cglib之后讲框架才讲。

JDK的java.lang.reflect包里有一个类Proxy,它提供一个newProxyInstance的静态方法,利用这个方法就能动态产生代理对象,这个方法接受三个参数:

1、目标对象的类加载器

2、目标类型所实现的接口

3、产生一个调用处理器,通过匿名内部类的形式,重写invoke方法,该方法里实现:当代理对象的方法被调用的时候,需要做什么事情。也就是这里的相当于在定制代理对象执行的方法实现部分。你可以让它调用目标对象的实现(反射的方式),也可以让它加上自己的特有处理,也可以两者结合。

需要关注:

1、动态代理的使用效果;

在编译期,我们并没有书写一个充当代理的类型。而是在运行期,通过反射等底层操作自动生成这样的代理对象。

这个代理对象拥有和目标对象来自于同一个接口里的所有方法。但是调用后效果可以有差异性;

2、JDK里实现动态代理的API,能够理解清楚这些API,包括参数、返回的意义和作用。

Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)三个参数:

  • 参数1:ClassLoader loader 类加载器——用哪个加载器来完成对代理对象的加载产生。——通常都是使用目标类的加载器。
  • 参数2:Class[] interfaces 接口数组——对目标身上的哪些方法被调用的时候进行代理——写在这些接口上的方法才能触发代理。
  • 参数3:InvocationHandler对象——调用处理器——里面的invoke方法实现调用后,代理要执行的代码。

返回 invoke(三个参数):

  • 参数1:proxy:代理对象;一般没有使用;除非是代理再套代理,这时它就成了目标;
  • 参数2:Method:代表的是本次被触发的方法对象。主要用于让目标对象去执行它。
  • 参数3:args:形参列表——是外部调用者传递给第二个参数Method的实参。

返回——是调用完代理以后,返回给客户程序的返回值——通常直接返回目标对象执行结束后的返回值——当然也可以再次进行特殊处理。

3、代理能做什么事情?

3-1代理可以完全颠覆目标设计人员的方法实现,完全采用新实现。

3-2代理可以再目标设计人员的方法实现的前后,添加新的功能。

3-3代理可以把目标真实的返回值进行更改。

/*
动态代理也要先设计出一个共有的接口
它的作用是告知哪些方法需要被代理。
 */
public interface IDynUser {
    void pay();
}
/*
不用去写代理类型,而是定义一个代理工厂类
由这个代理工厂类动态的去根据目标产生代理对象。
也是一个固定代码了
 */
public class DynProxyFactory {
    private Object target;//产生代理对象必须要依赖目标,所以需要先由一个目标对象。

    public DynProxyFactory(Object target){
        this.target = target;
    }

    //书写一个方法,返回一个代理对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return method.invoke(target,args);
                    }
                });
    }
}
/*
目标对象,通过实现接口,实现自己的业务行为
 */
public class DynBoss implements IDynUser{
    @Override
    public void pay() {
        System.out.println("老总付款");
    }
    /*
    没有实现接口的方法,代理里是没有的。
     */
    public void work(){
        System.out.println("工作");
    }
}
public class TestProxy {
    public static void main(String[] args) {
        DynProxyFactory proxyFactory = new DynProxyFactory(new DynBoss());
        IDynUser obj = (IDynUser) proxyFactory.getProxyInstance();
        obj.pay();
    }
}

动态代理总结:

虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理(我们要使用被代理的对象的接口),因为它的设计注定了这个遗憾。

3、行为模式——在类与类之间进行职责的划分,把行为进行拆分,然后拥有更换的分离复用效果。 

行为模式关注的是类当中某个行为的变化度,我们把这些可能出现扩展变化的行为进行单独抽离,设计为抽象。

 模板方法模式(Template Method)

问题域:

再某个行为当中,整个行为的流程是确定的,但是中间的某个或某几个步骤算法可能存在变化性。那么我们把整个行为的流程固定下来,把可能出现变化的中间步骤抽取成独立的抽象方法。

模板方法模式的解决方案无需把发生变化的整个方法进行抽象化设计,而是把方法里会发生变化的流程提取成单独的抽象方法,在业务行为里调用。那么这个业务行为就是所谓的”模板方法“。它固化了整个业务的流程和不会发生变化的步骤。(就是所谓的模板)。然后子类只需要重写会发生变化的步骤方法即可。

public abstract class Bussiness {

    /*
        这个方法就是模板方法;它保持了整个流程的固定性,
        以及不会发生变化的步骤的复用性.
     */
    public void service(){
        System.out.println("1、先接收用户的货物清单");
        for(int i = 0; i < 10; i++){
            System.out.println("2、遍历清单中的货物");
            getGoodPrice();
        }
        System.out.println("4、根据总价向用户进行展示");
    }

    public abstract double getGoodPrice();

}

class SpringBus extends Bussiness{
    public double getGoodPrice(){
        int goodPrice = 80;
        System.out.println("根据春节的活动计算价格");
        return goodPrice * 0.8;
    }
}

面试地位:

出现几率不高,但是模板方法模式有一个特点:它是少有的用继承而不是组合聚合解决问题的设计模式。

策略模式(Strategy)

策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口 和 具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个if判断都可以理解为就是一个策略。本模式使得算法可独立于使用它的用户而变化。

问题域:

对于某个算法具有多种实现形式,需要针对于不同的外部环境选择适用的某种算法其中的一种。如果我们直接在if-else if 这种分支语句里直接书写每种算法,很明显不利于扩展性。因此我们设计一个策略类,把提出抽象的算法方法,然后让各个子类去实现它。然后通过组合的方式跟外部环境进行关联。

策略模式的关键是把环境(Context——上下文)和策略的实现进行了分离。通过组合的形式进行关联。

策略模式优缺点

1)优点

  • 策略模式提供了对“开闭原则”的完美支持,用户可以在不 修改原有系统的基础上选择算法或行为,也可以灵活地增加 新的算法或行为。
  • 策略模式提供了管理相关的算法族的办法。
  • 策略模式提供了可以替换继承关系的办法。
  • 使用策略模式可以避免使用多重条件转移语句。

2)缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一 定程度上减少对象的数量。

面试地位:

几乎为0。但是策略模式在后面的框架学习经常出现。

Java设计模式——策略模式_塔塔开!!!的博客-CSDN博客_java 策略模式策略模式1.策略模式简介策略模式:策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口 和 具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个if判断都可以理解为就是一个策略。本模式使得算法可独立于使用它的用户而变化2.模式结构策略模式包含如下角色: Strategy: 抽象策略类:策略是一个接口,该接口定义若干个算法标识,即定义了若干个抽象方法(如下图的algorithm())Context: 环境类 /上下文类:上下文是依赖于接口的类(https://blog.csdn.net/qq_54773252/article/details/121032404?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166486761916800182128841%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166486761916800182128841&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-121032404-null-null.142^v51^control,201^v3^add_ask&utm_term=%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4187

 观察者模式(Observe)

问题域:

当一个对象的内部状态发生改变时能够主动通知观察者对象,让它做出及时的响应。

观察者模式其实就是我们之前遇到的事件监听器的行为模式,只是事件监听器是专用于GUI事件处理的。非GUI对象是用不了的。所以我们需要学习的是Java当中提供的另一套API。

在这套API里主要是一个类和一个接口。类是Observable,用来指定“被观察对象”的;接口是Observer,用来定义“观察者对象”的。当然,两者也是需要进行绑定的。

概念:

观察者模式也称为监听模式,即观察与被观察的关系,比如你在烧开水时看它有没有开,你就是观察者,水就是被观察者,观察者模式是指对象之间一对多的依赖关系,每当那个特点对象改变状态时,所有依赖它的对象都会得到通知并被自动更新。

观察者模式是对象的行为模式。

观察者模式可以有任意多个观察者对象同时监听某一个对象。监听某个对象的叫观察者(Observer),被监听的对象叫做被观察者(Subject)。被观察者对象在内容或状态发生改变时,会通知所有观察者,使它们能自动更新自己的信息。

//观察者
public class PriceObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof Product){
            Product product = (Product) o;
            double price = product.getPrice();
            double oldPrice = Double.parseDouble(arg.toString().split("[=]")[1]);
            System.out.println("通知用户,价格变化了,降价:" + (oldPrice - price));
            //打印结果为:通知用户,价格变化了,降价:18900.0      
          }
    }
}
//被观察对象
public class Product extends Observable {
    private String name;
    private int num;
    private double price;

    public Product(String name, int num, double price) {
        this.name = name;
        this.num = num;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        double oldPrice = this.price;

        this.price = price;
        setChanged();//设置观察点
        notifyObservers("oldPrice = " + oldPrice);//唤醒观察者
    }
}
public class TestMain {
    public static void main(String[] args) {
        Product product = new Product("外星人电脑",5,21000);
        PriceObserver priceObserver = new PriceObserver();

        product.addObserver(priceObserver);//绑定

        product.setPrice(2100);

        //第二种写法
        //product.addObserver(new PriceObserver());
        //product.setPrice(2100);
    }
}

5、架构模式(三层架构)

架构模式是宏观的,站在工程角度去设计各种我们的分工,关联,包结构等等,现阶段只要掌握”三层架构“。

三层架构模式的全称是:基于事务脚本的三层架构模型。这里的“事务”不等于数据库的事务,虽然有一点关系,但是不能划等号,这里的事务指代的是用户在这个使用过程中每一次和后台交互,就是一次“事务”。

每个事务的处理需要经过”三层“。

首先从表现层接受请求,校验数据,封装成Bean对象;然后调用业务层的方法完成业务处理,如果在业务处理中需要用到数据库(或文件)等持久化技术;这时调用持久层的方法去完成。

持久层做完动作之后,会把结构返回给业务层,业务层继续做业务处理。直到业务层得到最终结果,再把这个结果返回给表现层。表现层根据得到的返回结果,展示新的界面或刷新原有界面。

所以在整个过程当中,一共有四个参与的分类:表现层、业务层、持久层、在层与层之间进行数据传递的Bean对象(DTO对象——数据传输对象)。

    DTO      DTO
​
表现层——> 业务层——> 持久层
​
表现层 <——业务层 <——持久层
​
      DTO      DTO

1、表现层:

主要是视图组件、控制器组件、模型组件三个部分组成。

视图组件(View):主要是界面的外观及样式等等......目前在我们这个阶段主要用的是GUI当中的各种窗体、面板、按钮......;以后就要替换成Web的客户端技术(HTML/CSS)。

控制器组件(Controller):它的任务是控制本次请求交给哪个业务进行处理,业务处理以后如何跳转或刷新视图。目前是使用的监听器ActionListener这样的监听器对象;下个阶段的课程,就要替换成Web的服务器端技术——Servlet。

模型组件(Model):就是我们的Bean对象,它的任务是把视图收集到的数据进行封装。这个动作无论是现在还是以后不变了。

这三者的配合就是著名的”MVC“模型。MVC是三层架构里表现层的三种组件的组织模型。而不是三层架构的三个层!!!

2、业务层:

主要是接收表现层的调用,完成本次请求对应的业务处理。现在如何去设计以及如何去实现,以后没有变化;

3、持久层:

业务层处理过程中需要用到数据库,那么会调用持久层。持久层同样使用现在的方式:定义接口,书写实现类。现在在实现类的每个方法当中,我们都使用的是JDBC的原生代码。那么,下个阶段,这里会改成ORM框架。

ORM框架是”对象-关系映射“,它帮助我们简化我们的JDBC操作,因为在JDBC原生操作当中,我们需要书写大量的代码,来表示哪个Bean对应哪张表,Bean里面的哪个属性,对应表里面的哪个字段。甚至再高级一点的框架,连SQL语句都自动生成。

4、现在我们再关联3层的时候,是我们自己在表现层new出业务层的类,在业务层new出持久层的类......那么下个阶段会学习到新的框架,帮助我们自动管理这些对象的产生以及关联关系。

你可能感兴趣的:(JavaSE基础,软件工程)