02.07_学习Java的day24(详解)

今日主题:开发原则和设计模式

一、设计模式

1、什么是设计模式?

设计模式(Design Pattern)是一套被反复使用,多数人知晓的,经过分类总结的代码设计经验。

菜鸟和高手的区别在于高手受过很多伤(之前犯过很多错,踩过很多坑),这些高手不想新人也去
继续犯他们的错误,就把他们的开发经验总结出来,体现在代码设计方面就是设计模式。

GOF又称Gang of Four:四人帮
(即Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides),
《设计模式:可复用面向对象软件的基础》提出了23种设计模式。

2、为什么设计模式这么厉害?

(1)设计模式不仅仅是针对一种编程原因的,适用于所有编程语言的一套经验。
C,Java,Python等都可以用这套经验。

(2)考虑了代码的
可重用性:高
可读性:易读
可扩展性:高
可靠性:高

总的一句话:高内聚低耦合。

3、学习设计模式有几个阶梯?

第1层:刚开始学编程不久,听说过什么是设计模式
第2层:有很长时间的编程经验,自己写了很多代码,其中用到了设计模式,但是自己却不知道
第3层:学习过了设计模式,发现自己已经在使用了,并且发现了一些新的模式挺好用的
第4层:阅读了很多别人写的源码和框架,在其中看到别人设计模式,并且能够领会设计模式的精妙和带来的好处。
第5层:代码写着写着,自己都没有意识到使用了设计模式,并且熟练的写了出来。

二、开发原则

1、开发原则是干什么的?

保证代码高内聚低耦合,使得代码有高扩展性、高复用性、高可读性、高灵活性等等。

设计模式就是遵循开发原则的。

2、有哪些开发原则呢?(面向对象的开发原则)

1、开闭原则【Open Close Principle,缩写是OCP】:对修改关闭对扩展开发。

例如: 小明去医院看病,医生开了阿司匹林药花了20元, 第二天和朋友聚会聊起这事,
小红说道:不对呀,前几天我在医院也拿了阿司匹林药,才 14 块钱呢。
小花说:奇怪了,我买的是 16 块钱。小杰回应:怎么我买的是 18 块。
怎么这药这么多个价格。

问题的原因:现价格跟社保有关。
小明没有社保,小红社保是一档,小花社保是二挡,小杰社保是三挡。
如何开发这样的医院的收费系统呢?

如果社保等级有变化,不止1-3挡了。还要增加4,5等等。
涉及到修改Patient的pay方法。违反了对修改关闭的原则。

为了保证代码的扩展性,对代码重新设计。
因为主要是由于病人不同(他们的社保等级不同),导致药品的价格不同,
那么我们可以把病人分为很多类。
病人的类型:一等社保病人,二等社保病人。。。

Patient:子类(OneLevelSocialSecurityPatient、TwoLevelSocialSecurityPatient…)

public class TestPrinciple1 {
    //程序入口
    public static void main(String[] args) {
        //创建不同的病人
        Patient ming = new Patient("小明");
        Patient hong = new Patient("小红",1);
        Patient hua = new Patient("小花",2);
        Patient jie = new Patient("小杰",3);

        Hospital hospital = new Hospital();
        hospital.saleMedicine(ming);
        hospital.saleMedicine(hong);
        hospital.saleMedicine(hua);
        hospital.saleMedicine(jie);
    }
}

//药物类型
//药物的熟悉:名称、价格、批次、厂家、疗效....
class Medicine {
    private String name;
    private double price;
    public Medicine(String name, double price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public double getPrice() {
        return price;
    }
}

//接口,针对病人的处理接口
//这个医院的收费系统,针对病人需要提供什么服务呢?
//(1)获取病人的姓名
//(2)需要付多少钱,针对什么药
interface IPatient{
    String getName();
    double pay(Medicine medicines);
}

//病人:关注两个属性:姓名,社保等级
class Patient implements IPatient{
    private String name;
    private int level;//默认值是0

    //如果使用这个构造器,表示病人的社保等级是0,即没有社保
    public Patient(String name) {
        this.name = name;
    }
    //手动指定社保等级
    public Patient(String name, int level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public double pay(Medicine medicines) {
        if (level == 1) {
            return medicines.getPrice()*0.7;
        } else if (level == 2) {
            return medicines.getPrice()*0.8;
        } else if (level == 3) {
            return medicines.getPrice()*0.9;
        }
        return medicines.getPrice();
    }
    @Override
    public String getName() {
        return name;
    }
}

//医院系统的管理类型
class Hospital {
    //这里用其中一个药品做演示
    private Medicine medicine = new Medicine("阿司匹林", 20.0);

    //卖药,根据病人的不同,卖药的处理不一样
    public void saleMedicine(IPatient patient) {
        double money = patient.pay(medicine);
        System.out.println(patient.getName() + " 花了 " + money + "块钱买了药:" + medicine.getName());
    }

}

如果此时增加社保等级,还需要修改原来的类吗?
不需要修改原来的类,只需要增加一个子类即可。

对修改关闭,对扩展开放。

public class TestPrinciple1_2 {
    public static void main(String[] args) {
        //创建不同的病人
        Patient ming = new Patient("小明");
        Patient hong = new OneLevelSocialSecurityPatient("小红");
        Patient hua = new TwoLevelSocialSecurityPatient("小花");
        Patient jie = new ThreeLevelSocialSecurityPatient("小杰");

        Hospital hospital = new Hospital();
        hospital.saleMedicine(ming);
        hospital.saleMedicine(hong);
        hospital.saleMedicine(hua);
        hospital.saleMedicine(jie);
    }
}
//药物类型
//药物的熟悉:名称、价格、批次、厂家、疗效....
class Medicine {
    private String name;
    private double price;
    public Medicine(String name, double price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public double getPrice() {
        return price;
    }
}

//接口,针对病人的处理接口
//这个医院的收费系统,针对病人需要提供什么服务呢?
//(1)获取病人的姓名
//(2)需要付多少钱,针对什么药
interface IPatient{
    String getName();
    double pay(Medicine medicines);
}

//病人:关注两个属性:姓名,社保等级
class Patient implements IPatient {
    private String name;
    private int level;//默认值是0

    //编译器不会自动增加无参构造了
    //如果使用这个构造器,表示病人的社保等级是0,即没有社保
    public Patient(String name) {
        this.name = name;
    }
    //手动指定社保等级
    public Patient(String name, int level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public double pay(Medicine medicines) {
        //默认的病人类型,返回药品的原价
        return medicines.getPrice();
    }
}

//OneLevel:一等
//SocialSecurity:社保
//子类继承父类时,一定会在子类的构造器的首行,自动调用父类的无参构造,
// 如果父类没有无参构造,那么需要手动调用父类的有参构造
class OneLevelSocialSecurityPatient extends Patient{
    public OneLevelSocialSecurityPatient(String name){
        super(name,1);
    }
    @Override
    public double pay(Medicine medicines) {
        //一等的病人类型,返回药品的原价
        return medicines.getPrice()*0.7;
    }
}

class TwoLevelSocialSecurityPatient extends Patient{
    public TwoLevelSocialSecurityPatient(String name){
        super(name,2);
    }
    @Override
    public double pay(Medicine medicines) {
        //二等的病人类型,返回药品的原价
        return medicines.getPrice()*0.8;
    }
}
class ThreeLevelSocialSecurityPatient extends Patient{
    public ThreeLevelSocialSecurityPatient(String name){
        super(name,3);
    }
    @Override
    public double pay(Medicine medicines) {
        //三等的病人类型,返回药品的原价
        return medicines.getPrice()*0.9;
    }
}
//医院系统的管理类型
class Hospital {
    //这里用其中一个药品做演示
    private Medicine medicine = new Medicine("阿司匹林", 20.0);

    //卖药,根据病人的不同,卖药的处理不一样
    public void saleMedicine(IPatient patient) {
        double money = patient.pay(medicine);
        System.out.println(patient.getName() + " 花了 " + money + "块钱买了药:" + medicine.getName());
    }

}

2、单一职责原则【Single Responsibility Principle,缩写是SRP】:

小到一个方法,往大了说,一个类,一个包,一个模块,都负责一个职责。
例如:
Math.sqrt(x):它只负责求平方根的功能
Math类,只负责和数学计算有关的。
之前有个同学提出过这样的一个问题?
为什么JDK的核心类库中,不把所有的工具方法集中到一个类中?
例如:数学计算,数组计算,集合计算…,这些方法都是静态方法,
为什么不合并到一个类中,我们就不需要记好几个类了。

违反了单一职责的原则。

包:
java.util:各种工具,包括集合
java.net:和网络编程有关的
java.sql:和数据库操作有关的

这里主要讨论的是类。
例如:
分层(见课堂笔记的图片)

单一职责:各司其职,好管理
可复用性更好。

3、里氏替换原则:【Liskov Substitution Principle,缩写是LSP】

任何时候都可以用子类型来替换父类型。
换句话说,用子类的类型替换父类的类型时,功能不受影响。

例如:
动物的信息管理系统:涉及到鸟类、燕子类、企鹅类等信息管理。

生物学中,燕子和企鹅都属于鸟类
但是在程序中,这么设计不一定合适。

在程序中还得遵守里氏替换原则。

继承有优点也有缺点?
缺点:无论子类是否需要这个方法,一旦继承,就会继承所有的方法等特征。
要是is-a的关系才能继承。而且要考虑里氏替换原则。

public class TestPrinciple3 {
    public static void main(String[] args) {
        Swallow s = new Swallow();//燕子
        Penguin p = new Penguin();//企鹅

//        我们要观察他们的行为
        look(s);
        look(p);
    }

    public static void look(Bird bird){
        bird.fly();
        bird.walk();
    }
}

//鸟的类型
class Bird{
    public void walk(){
        System.out.println("走");
    }
    public void fly(){
        System.out.println("飞");
    }
}

//燕子的类型
class Swallow extends Bird{//燕子
    //...
}

//企鹅的类型
class Penguin extends Bird{//企鹅不会飞,所以选择重写fly()

    //不应该有这个方法
    public void fly(){
        throw new RuntimeException("企鹅不会飞");
    }
}
public class TestPrinciple3_2 {
    public static void main(String[] args) {
        Swallow s = new Swallow();//燕子
        Penguin p = new Penguin();//企鹅

        look(s);

        look(p);
    }

/*    public static void look(Bird bird){
        bird.fly();
        bird.walk();
    }

    public static void look(Animal bird){
        bird.walk();
    }*/

    public static void look(Animal a){
        a.walk();

        if(a instanceof Bird){
            Bird b = (Bird) a;
            b.fly();
        }
    }
}
//这个例子中,所有类型都会走,把走这个行为,提取到公共父类中
class Animal{
    public void walk(){
        System.out.println("走");
    }
}

class Bird extends Animal{
    public void fly(){
        System.out.println("飞");
    }
}
class Swallow extends Bird{//燕子
    //...
}

class Penguin extends Animal{//企鹅
    //...
}

4、依赖倒置原则【Dependence Inversion Principle,缩写是DIP】

要依赖抽象类或接口,而不是依赖具体类型。
主要是指:形参的类型、返回值的类型、变量的引用类型。

即面向抽象编程(抽象类或接口)

例如:
Map接口的API:
Set entrySet()
Collection values()

为什么要这么设计?
例如,(1)上面的Map,在HashMap中,重写Set entrySet()时,可以返回HashSet。
在TreeMap中,重写Set entrySet()是,可以返回TreeSet。
更灵活。
我们调用这个方法,是为了得到所有的(key,value),或者所有的key,要遍历显示他们,
但是至于你内部怎么存我们不关心,对于Set,Collection我们知道统一的遍历方式。

(2)
public void saleMedicine(IPatient patient) {
double money = patient.pay(medicine);
System.out.println(patient.getName() + " 花了 " + money + “块钱买了药:” + medicine.getName());
}
这里形参使用 IPatient接口,可以接受他的各种实现类。

5、接口隔离原则【Interface Segregation Principle,缩写是ISP】

之前依赖倒置原则说,要尽量面向接口、抽象类编程,
那么如何设计接口呢?

例如:
Inter接口,包含:m1,m2,m3,m4四个抽象方法。
B类和D类都是Inter的实现类。

现在有A类要依赖(使用)Inter接口。
A类依赖了Inter接口的m1,m2,m3方法。

C类依赖了Inter接口的m2,m3,m4方法。

通过Inter接口,A类与C类“间接”与B类和D类建立关系。没有发生耦合。

功能没问题,但是违反了最小依赖原则。即接口的隔离不够小。

我们学习过的接口中,是否有这样的设计在里面?
自然比较接口:java.lang.Comparable接口,int compareTo(T t)
定制比较接口:java.util.Comparator接口,int compare(T t1, T 2)
序列号接口:java.io.Serializable接口

Collection集合,会细分为List和Set接口。
因为List系列的都要提供和[index]有个的操作,都是有序的。
因为Set系列的都要保证不可重复。

public class TestPrinciple5 {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        C c = new C();
        D d = new D();

        a.test01(b);
        a.test02(b);
        a.test03(b);
        System.out.println("-------------------");
        c.test01(d);
        c.test02(d);
        c.test03(d);
    }
}

//这里的四个方法,不适用于所有的场景。
//某些类与我依赖,只依赖1,2,3,某些只依赖2,3,4,或者还有只依赖2,3等
interface Inter{
    void m1();
    void m2();
    void m3();
    void m4();
}
class B implements Inter{

    @Override
    public void m1() {
        System.out.println("B类实现了Inter接口的m1方法");
    }

    @Override
    public void m2() {
        System.out.println("B类实现了Inter接口的m2方法");
    }

    @Override
    public void m3() {
        System.out.println("B类实现了Inter接口的m3方法");
    }

    @Override
    public void m4() {
        System.out.println("B类实现了Inter接口的m4方法");
    }
}
class D implements Inter{

    @Override
    public void m1() {
        System.out.println("D类实现了Inter接口的m1方法");
    }

    @Override
    public void m2() {
        System.out.println("D类实现了Inter接口的m2方法");
    }

    @Override
    public void m3() {
        System.out.println("D类实现了Inter接口的m3方法");
    }

    @Override
    public void m4() {
        System.out.println("D类实现了Inter接口的m4方法");
    }
}

//A类依赖了Inter接口的m1,m2,m3方法。
//因为接口是不能创建对象,最后要给这些方法传入接口的实现类B的对象
class A{
    //形参使用接口类型,因为遵循依赖倒置原则
    public void test01(Inter inter){
        inter.m1();//里面使用了接口的m1方法
    }

    public void test02(Inter inter){
        inter.m2();//里面使用了接口的m2方法
    }

    public void test03(Inter inter){
        inter.m3();//里面使用了接口的m3方法
    }
}

//C类依赖了Inter接口的m2,m3,m4方法。
class C{
    //形参使用接口类型,因为遵循依赖倒置原则
    public void test01(Inter inter){
        inter.m2();//里面使用了接口的m2方法
    }

    public void test02(Inter inter){
        inter.m3();//里面使用了接口的m3方法
    }

    public void test03(Inter inter){
        inter.m4();//里面使用了接口的m4方法
    }
}
public class TestPrinciple5_2 {
}
//某些类与我依赖,只依赖1,2,3,某些只依赖2,3,4,或者还有只依赖2,3等
interface Inter1{
    void m1();
}
interface Inter2{
    void m2();
    void m3();
}
interface Inter3{
    void m4();
}

//B只提供1,2,3
class B implements Inter1,Inter2{
    @Override
    public void m1(){
        System.out.println("B类实现m1");
    }

    @Override
    public void m2(){
        System.out.println("B类实现m2");
    }

    @Override
    public void m3(){
        System.out.println("B类实现m3");
    }
}
//D类只提供2,3,4
class D implements Inter2,Inter3{

    @Override
    public void m2() {
        System.out.println("D类实现m2");
    }

    @Override
    public void m3() {
        System.out.println("D类实现m3");
    }

    @Override
    public void m4() {
        System.out.println("D类实现m4");
    }
}

//A只想依赖Inter接口的1,2,3,当然是通过接口间接依赖的
//传入B类对象就是符合的
class A {
    public void test01(Inter1 inter){
        inter.m1();
    }
    public void test02(Inter2 inter){
        inter.m2();
    }
    public void test03(Inter2 inter){
        inter.m3();
    }
}
//C只想依赖Inter接口的2,3,4,当然是通过接口间接依赖的
//传入D类对象就是符合的
class C{
    public void method01(Inter3 inter){
        inter.m4();
    }
    public void method02(Inter2 inter){
        inter.m2();
    }
    public void method03(Inter2 inter){
        inter.m3();
    }
}

6、迪米特法则【Low of Demeter,缩写是LOD】:

或者也称为最少知识原则(Least Knowledge Principle)
解释:
即一个对象应当对其他对象有尽可能少的了解,目的是保证低耦合。

经典台词:知道的太多,对你没好处。

例如:我们在使用集合时,Collection时,
有一个方法:Iterator iterator()方法。
我们不需要知道ArrayList集合中,到底是用什么类型来实现Iterator迭代器。
public class TestPrinciple6 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("小贾");
        list.add("小张");

        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

7、组合/聚合复用原则:【Composition/Aggregation Reuse Principle(CARP) 】

尽量使用组合和聚合,少使用继承的关系来达到复用的原则。

继承的优点:代码的复用和扩展
继承的缺点:类的关系过于紧密(无论你是否需要父类中的某些方法等成员,只要继承它了,就会继承所有的方法等成员)
会知道父类的更多的实现细节。

类与类之间的关系:
(1)依赖:只要用到它,在它的代码中出现了,就是依赖
(2)关联关系:有一个成员变量用到了某个类型
持久关系,需要持久化,即存储到数据库中的关系
class Empolyee{
String name;
Computer computer;
}
class Computer{
}
其中Empolyee依赖/关联Computer,但是,可以没有computer对象,
例如某些员工不需要领用电脑设备。
(3)聚合:是更为紧密的关联
class A{
B b;
C c;
}
其中A由B和C组成,A离不开B和C的对象,离开了就不完整了。
但是反过来,B和C可以单独存在。
class Car{
private Engine engine;//引擎
private Tyre[] tyres;//轮胎
}
(4)组合:更进一步的关联
class A{
B b;
C c;
}
其中A由B和C组成,A离不开B和C的对象,离开了就不完整了。
而且B和C离开A也没有意义。
class Window{
private Menu menu;//菜单
private Slider slider;//滑动条
private Panel panel;//工作区
}
(5)继承
类与类之间继承,
实现类与接口之间实现关系也是看成继承。
class A{
}
class B extends A{
}
B继承了所有A的方法,成员变量等,没有继承的构造器也要调用。

在设计代码的时候:
(1)能不依赖的不依赖
(2)能够降低依赖的就降低依赖程度

就如结婚比恋爱时关系紧密了,不自由了。
陌生人之间没关系也就没矛盾。
为什么有的人只敢与喜欢的人做朋友,不能做恋人,因为朋友比恋人关系远一点,
不然一旦分手就不复相见。

三、设计模式

1、23种设计模式

分为三大类:
(1)创建型:单例、原型、工厂方法、抽象工厂、建造者(Builder)
(2)结构型:代理、适配器、桥接、装饰、外观、享元、组合
(3)行为型:模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器

2、今天要学习的

(1)创建型:单例和工厂方法
(2)结构型:代理和装饰
(3)行为型:模板、观察者、迭代器

(一)单例设计模式————创建型

面试高频题

1、单例?
例:实例instance,对象
单例:唯一的对象
单例设计模式:某个类在整个系统中只有唯一的一个对象。

2、如何实现单例?

回忆:
匿名对象:表示对象没有赋值给变量,即没有取名字,不是惟一的 对象。
匿名内部类确实只有唯一的对象。
枚举:枚举是表示某个类型的对象是有限的固定的几个。

第一类:恶汉式单例设计模式(或饿汉式单例设计模式)
————不管是不是需要这个类的对象,都提前创建好这个类的对象
饿:饥不择食,非常着急,
恶:一言不合就开干

(1)JDK1.5之后的枚举形式
例如:
enum EnumSingle{
INSTANCE
}

(2)JDK1.5之前的枚举形式
class Singleton{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){

}
public static Singleton getInstance(){
    return INSTANCE;
}

}
(3)
class Single{
public static final Single INSTANCE = new Single();
private Single(){}
}

第二类:懒汉式单例设计模式
懒:不到万不得已,不去做。拖到不得不创建对象。
(1)形式一
class Lazy{
//不着急创建对象
//此时不能加final,因为final必须初始化
private static Lazy instance;//默认值是null

//构造器私有化
private Lazy(){

}

//提供一个方法,来获取这个唯一的对象
public static Lazy getInstance(){
     if(instance == null) {//当instance为空时,要竞争锁
        synchronized (Lazy.class) {
            if (instance == null) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                instance = new Lazy();
            }
        }
    }
}

}

(2)形式二:
饿汉式是在类初始化的时候创建对象,类初始化由类加载器等完成,他一定是线程安全的。
懒汉式又希望延迟创建对象。
class Only{
private Only(){

}

public static Only getInstance(){
    return Inner.INSTANCE;
}

private static class Inner{
    static final Only INSTANCE = new Only();
}

}

public class TestSingle {
    @Test
    public void test01(){
        //获取EnumSingle类型的对象
        EnumSingle obj1 = EnumSingle.INSTANCE;
        EnumSingle obj2 = EnumSingle.INSTANCE;

        System.out.println(obj1 == obj2);//true  比较对象的地址,因为只有唯一的一个,获取的对象是同一个
        System.out.println(obj1.equals(obj2));//true  equals()如果没有重写,和==一样的
    }

    @Test
    public void test02(){
        //获取Singleton的对象
        Singleton obj1 = Singleton.getInstance();
        Singleton obj2 = Singleton.getInstance();

        System.out.println(obj1 == obj2);
    }

    @Test
    public void test03(){
        //获取Single的唯一对象
        Single obj1 = Single.INSTANCE;
        Single obj2 = Single.INSTANCE;
        System.out.println(obj1 == obj2);
    }

    @Test
    public void test04(){
        //获取Lazy的对象
        Lazy obj1 = Lazy.getInstance();
        Lazy obj2 = Lazy.getInstance();

        System.out.println(obj1 == obj2);
    }

    //现在演示多线程获取Lazy的对象
    //这里使用成员变量obj1,obj2的原因,是因为我们要在匿名内部类中使用这个两个变量
    Lazy obj1;
    Lazy obj2;
    @Test
    public void test05(){
        //获取Lazy的对象
        Thread t1 = new Thread(){
            public void run(){
                obj1 = Lazy.getInstance();
            }
        };
        t1.start();
        Thread t2 = new Thread(){
            public void run(){
                obj2 = Lazy.getInstance();
            }
        };
        t2.start();

        //这里要加join,确保两个匿名内部类的线程执行完成之后,再运行以下代码
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("obj1="+ obj1);
        System.out.println("obj2="+ obj2);
        System.out.println(obj1 == obj2);
    }

}
//(1)形式一:枚举形式
enum EnumSingle{
    INSTANCE
}

//(2)形式二:类似于JDK1.5之前创建枚举的形式
class Singleton{
    //INSTANCE的数据类型是Singleton,因为表示的是Singleton的对象
    //INSTANCE大写是因为常量名建议大写
    //final:唯一的对象,固定的,不修改的
    //static:因为getInstance()方法需要静态,通过类名.进行调用,静态方法只能访问静态成员
    //private:表示不对外直接暴露唯一的对象
    private static final Singleton INSTANCE = new Singleton();

    //构造器私有化之后,我们外面就不能创建它的对象了
    private Singleton(){

    }

    //写一个方法,为外界提供这个唯一的对象INSTANCE
    //返回值类型Singleton
    public static Singleton getInstance(){
        return INSTANCE;
    }

    public static void method(){
        System.out.println("静态方法");
    }
}

//形式三:简化第二种形式
class Single{
    public static final Single INSTANCE = new Single();
    private Single(){}
}

/*
懒汉式
 */
class Lazy{
    //不着急创建对象
    //此时不能加final,因为final必须初始化
    private static Lazy instance;//默认值是null

    //构造器私有化
    private Lazy(){

    }

    //提供一个方法,来获取这个唯一的对象
    public static Lazy getInstance(){
//        return new Lazy();//错误,每次调用都会new
        //不合适,有线程安全问题
/*        if(instance == null){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Lazy();
        }*/

        //锁对象:(1)任意类型的对象(2)保证多个线程使用同一个对象
        //这里选择Lazy.class
        //以下代码没有错误,但是不够完美
        /*synchronized (Lazy.class){
            if(instance == null){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                instance = new Lazy();
            }
        }*/

        //优化
        if(instance == null) {//当instance为空时,要竞争锁
            synchronized (Lazy.class) {
                if (instance == null) {
                    //不能去掉,原因是因为一开始可能有多个线程判断了instance为空
                    //其中一个线程获得锁,创建对象,等他释放锁之后,其他线程获得锁,仍然要判断
                    //否则就会出现多个对象。
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Lazy();
                }
            }
        }
        return instance;
    }
}

/*
在外部类Only初始化时,是不会初始化Inner的,因此此时没有创建Only对象。
当我们调用getInstance方法时,获取Inner.INSTANCE;,才会初始化Inner,才会创建Only对象,
既保证了延迟创建,又保证了线程安全
 */
class Only{
    private Only(){

    }

    public static Only getInstance(){
        return Inner.INSTANCE;
    }

    private static class Inner{
        static final Only INSTANCE = new Only();
    }

}

(二)工厂设计模式————创建型

1、为什么要使用工厂设计模式?
目标:把对象的创建者(new)与对象的使用者分开。
或者换句话说,把创建对象交给更专业的人(类)来做。

生活中:
最早的社会:自给自足
打猎,用树叶拼凑衣服
在后来:男耕女织
现在:分工更明显

代码中:创建对象这个事情,也不是所有人都合适的。
特别一下复杂的对象,例如:线程池对象,spring容器对象,数据库连接池对象等
这些对象需要读取很多配置文件,甚至有的还要读取系统信息,才能创建好一个对象。

所以,像这类对象,我们不要交给使用者创建,由专门的类(工厂类)来创建,
使用者只要获取即可。

之前总结过,要得到对象:
A:自己new
B:类名.常量对象
C:通过调用方法获取对象

2、工厂设计模式有很多种形式
(1)简单工厂模式
(2)工厂方法设计模式
(3)抽象工厂模式(今天不讲,非常复杂,适用于创建系列对象用的)

简单工厂模式:

例子:
(1)有一个接口:产品的标准
(2)有很多产品的具体类型
(3)声明一个工厂类
版本1:
public static Car createCar(String type){
switch (type){
case “宝马”:
return new BMW();
case “奔驰”:
return new Benz();
case “凤凰”:
return new FengHuang();
default:
return null;
}
}

当我增加产品,例如:红旗车,就要修改createCar()方法,
就违反了“开闭原则”,对修改关闭,对扩展开放。

形式二:
class SimpleFactory2{
public static Car createBMWCar(){
return new BMW();
}
public static Car createBenzCar(){
return new Benz();
}
public static Car createFengHuangCar(){
return new FengHuang();
}
}
增加新产品,例如:红旗车,再添加一个方法即可。对原来的代码不需要修改。
但是问题在于,产品如果很多,就会导致这个方法很多。
而且严格来说,也是修改了类了。

形式三:
使用反射,见下面的SimpleFactory3。

增加新产品,例如:红旗车,不需要修改代码,
但是如果要为产品的成员变量初始化的话,比较麻烦。

public class TestSimpleFactory {
    @Test
    public void test01(){
        //没有工厂的时候,要得到我们的具体的车的产品对象
        //TestSimpleFactory这里代表类的使用者,
        //它与BMW这里依赖了
        Car bm = new BMW();
        bm.run();
    }

    @Test
    public void test02(){
        //使用工厂
        //现在使用者TestSimpleFactory与具体的BMW类型解耦合了
        //至于在工厂内部如何造成复杂的宝马车,这里不需要知道
        Car bm = SimpleFatory.createCar("宝马");
        bm.run();
    }

    @Test
    public void test03(){
        Car bm = SimpleFactory2.createBMWCar();
        bm.run();
    }

    @Test
    public void test04(){
        Car bm = SimpleFactory3.createCar("com.atguigu.factory.test02.BMW");
        bm.run();
    }
}
//产品的标准
interface Car{
    void run();
}
//产品的具体类型
class BMW implements Car{

    @Override
    public void run() {
        System.out.println("坐在宝马车里哭");
    }
}
class Benz implements Car{
    @Override
    public void run() {
        System.out.println("坐在引擎盖上哭");
    }
}
class FengHuang implements Car{
    @Override
    public void run() {
        System.out.println("坐在自行车上哭");
    }
}
class SimpleFatory{
    //有一个方法,可以提供车的产品对象
    //重命名快捷键:shift+F6
    public static Car createCar(String type){
        switch (type){
            case "宝马":
                return new BMW();
            case "奔驰":
                return new Benz();
            case "凤凰":
                return new FengHuang();
            default:
                return null;
        }
    }
}

//提出解决方案之一:
class SimpleFactory2{
    public static Car createBMWCar(){
        return new BMW();
    }
    public static Car createBenzCar(){
        return new Benz();
    }
    public static Car createFengHuangCar(){
        return new FengHuang();
    }
}

//提出解决方案之二:
class SimpleFactory3{
    public static Car createCar(String type){
        try {
            //传类的具体的名称
            //使用反射
            //要求产品类型必须有“无参构造”
            Class clazz = Class.forName(type);
            return (Car)clazz.newInstance();
            //这里如果要为对象的成员变量赋值的话,这种方式比较麻烦
            //因为每一种类的成员变量不同
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }
}
工厂方法设计模式

如何实现?
(1)产品的标准接口
(2)具体的产品类,接口的实现类
(3)工厂的接口
(4)具体的工厂类

如果现在增加新产品类,例如:红旗车,不需要修改原来的类,
只要增加新的工厂类即可。
遵循了“开闭原则”,也遵循了“单一职责”原则等

public class TestFactoryMethod {
    @Test
    public void test01(){
        //通过宝马工厂获取宝马车对象时
        CarFactory cf = new BMWFactory();
        Car bm = cf.createCar();
        bm.run();
    }
}
interface Car{
    void run();
}
//产品的具体类型
class BMW implements Car{

    @Override
    public void run() {
        System.out.println("坐在宝马车里哭");
    }
}
class Benz implements Car{
    @Override
    public void run() {
        System.out.println("坐在引擎盖上哭");
    }
}

//工厂的接口
//CarFactory接口的实现类,都是用来生产Car的实现类的对象的
interface CarFactory{
    Car createCar();
}

//具体的工厂类
class BMWFactory implements CarFactory{

    //这里返回值类型BMW<Car,遵循重写的要求
    @Override
    public BMW createCar() {
        return new BMW();
    }
}
class BenzFactory implements CarFactory{

    @Override
    public Benz createCar() {
        return new Benz();
    }
}

(三)、代理设计模式

1、为什么要使用代理模式?
当访问对象(使用者)与最终的目标对象之间不能或不适合直接引用,
我们可以在它们中间加入代理。

生活:
我们买、租房子的时候,
我们和业主不方便直接见面,或者我根本就不清楚业主是谁。
那么我会找中介。
我们不适用挨家挨户的问,即麻烦,又不安全。
业主会更相信中介公司,他们会要卖或租的房子信息告知它们,
我们从中介获取信息。

例如:
线程。
我们使用者要启动线程,对线程进行操作,我们线程是要通过操作系统
底层的代码进行启动的。
大多数程序员是没有能力调用底层的代码。这么做也不安全。
Java就提供了一个Tread这个类作为“代理”,帮我们启动和操作线程。

所有要启动线程都要用到Thread类的start()。
不管是继承Thread类还是实现Runnable接口的方式。

2、如何实现代理?
代理模式有两种形式:静态代理模式和动态代理模式
不管哪一种代理,都有三个要素:
(1)被代理者
(2)代理者
(3)代理主题(接口)

静态代理模式

(1)被代理者
(2)代理者
(3)代理主题(接口)

如果被代理类很多,接口不同,
但是代理工作内容相同,例如:都是记录日志,统计时间等,
这个编写多个代理类太麻烦了,冗余的代码太多。

但是如果被代理类不同,代理工作也不同,
那么单独编写代理类是合适的。

public class TestStaticProxy {
    @Test
    public void test01(){
        //创建被代理者对象
        UserDAO ud = new UserDAO();
        //创建代理类对象
        UserDAOProxy udp = new UserDAOProxy(ud);
        //调用代理的add方法
        udp.add();
    }
}

//主题接口
//进行数据库操作的公共接口
interface DAO{
    void add();
//    void delete();
//    void update();
//    void select();
}

interface IService{
    //...
}

//被代理者
class UserDAO implements DAO{

    @Override
    public void add() {
        System.out.println("添加一个用户");
    }
}
//被代理者2
class GoodsDAO implements DAO{

    @Override
    public void add() {
        System.out.println("添加一个商品");
    }
}
//代理者
/*
代理工作:
    项目经理说,给所有DAO的实现类的所有方法,
    提供一个运行时间的测试和日子记录。
    计算每一个方法开始运行和结束运行的时间,计算时间差,
    并且打印xx方法开始运行

    这个功能不是一直都要的,而且是开发环境需要,上线时不要。
    不适合在上面的被代理类中直接加这个代码
 */
class UserDAOProxy implements DAO{
    //具体的被代理者对象
    private UserDAO ud;

    public UserDAOProxy(UserDAO ud){
        this.ud = ud;
    }
    @Override
    public void add() {
        System.out.println("UserDAO的add方法开始执行");
        long start = System.currentTimeMillis();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //运行实际的具体的业务代码
        //调用UserDAO的add方法
        ud.add();

        long end = System.currentTimeMillis();
        System.out.println("运行时间:"+(end-start)+"毫秒");
    }
}

class GoodsDAOProxy implements DAO{
    private GoodsDAO gd;
    public GoodsDAOProxy(GoodsDAO gd){
        this.gd = gd;
    }
    @Override
    public void add() {
        System.out.println("GoodsDAO的add方法开始执行");
        long start = System.currentTimeMillis();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //运行实际的具体的业务代码
        //调用GoodsDAO的add方法
        gd.add();

        long end = System.currentTimeMillis();
        System.out.println("运行时间:"+(end-start)+"毫秒");
    }
}
动态代理模式

(1)被代理者
(2)代理者
(3)代理主题(接口)
(4)代理工作处理器:要求必须实现InvocationHandler接口,
实现
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}

代理工作内容是相同的。
例如:都是记录日志,统计时间
(5)在使用代理类的位置“动态的”创建代理类及其他的对象
代理类是动态编译生成的,而不是事先写好的。
要求:使用java.lang.reflect.Proxy,
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
A:static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces)
先获取代理类的Class对象,然后再创建对象

B:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
直接获取代理类的对象,至于这个类在内存中待着就好了,我不获取他

public class TestDynamicProxy {
    @Test
    public void test01(){
        //创建被代理者对象
        UserDAO ud = new UserDAO();

        //获取被代理者UserDAO的类加载器对象
        Class clazz = ud.getClass();
        ClassLoader loader = clazz.getClassLoader();

        //获取被代理者UserDAO实现的所有接口
        Class<?>[] interfaces = clazz.getInterfaces();

        //创建代理工作处理器对象
        InvocationHandler h = new MyHandler(ud);//ud是被代理者
        //动态创建代理类对象
        //是UserDAO的代理类对像
        //newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
        //参数一:ClassLoader loader,类加载器,我们要加载一个类或动态生成一个类的Class都需要类加载器
        //      这里最好和被代理者使用同一个类加载器最好。
        //参数二:Class<?>[] interfaces,被代理者实现的所有的接口们
        //参数三:InvocationHandler h,代理工作处理器对象
//                代理者替被代理者完成xx工作
        //方法调用得到的是代理类的对象
        Object proxy = Proxy.newProxyInstance(loader,interfaces,h);
        //向下转型,为了调用add等方法
        DAO dao = (DAO) proxy;
        dao.add();
        Object user = dao.select();
        System.out.println("查询结果:" + user);
    }

    @Test
    public void test02(){
        //创建被代理者对象
        GoodsDAO ud = new GoodsDAO();

        //获取被代理者GoodsDAO的类加载器对象
        Class clazz = ud.getClass();
        ClassLoader loader = clazz.getClassLoader();

        //获取被代理者GoodsDAO实现的所有接口
        Class<?>[] interfaces = clazz.getInterfaces();

        //创建代理工作处理器对象
        InvocationHandler h = new MyHandler(ud);//ud是被代理者
        //动态创建代理类对象
        //是UserDAO的代理类对像
        //newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
        //参数一:ClassLoader loader,类加载器,我们要加载一个类或动态生成一个类的Class都需要类加载器
        //      这里最好和被代理者使用同一个类加载器最好。
        //参数二:Class<?>[] interfaces,被代理者实现的所有的接口们
        //参数三:InvocationHandler h,代理工作处理器对象
//                代理者替被代理者完成xx工作
        //方法调用得到的是代理类的对象
        Object proxy = Proxy.newProxyInstance(loader,interfaces,h);
        //向下转型,为了调用add等方法
        DAO dao = (DAO) proxy;
        dao.add();
        Object user = dao.select();
        System.out.println("查询结果:" + user);
    }

    @Test
    public void test03(){
        Other other = new Other();
        Class clazz = other.getClass();
        IService o = (IService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new MyHandler(other));
        o.fang();
        o.fun();
    }
}
//主题接口
//进行数据库操作的公共接口
interface DAO{
    void add();
//    void delete();
//    void update();
    Object select();
}

interface IService{
    void fang();
    void fun();
}

//被代理者
class UserDAO implements DAO{

    @Override
    public void add() {
        System.out.println("添加一个用户");
    }

    @Override
    public Object select() {
        return "用户对象";
    }
}
//被代理者2
class GoodsDAO implements DAO{

    @Override
    public void add() {
        System.out.println("添加一个商品");
    }

    @Override
    public Object select() {
        return "商品对象";
    }
}

//代理工作处理器
class MyHandler implements InvocationHandler{
    //被代理者,可能是GoodsDAO的对象,也可能是UserDAO的对象,也可能是其他被代理者对象
    //所以这里使用Object类型接收。
    private Object target;

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

    /**
     * 代理工作处理器必须重写的方法。这个方法不是程序员调用的,由被代理者工作被启动时自动调用的。
     * 我们需要在这个方法中,编写代理者要替被代理者完成xx工作内容
     * @param proxy Object:代理者对象
     * @param method   Method:代理者要替被代理代理的方法,例如:上面的add方法等
     * @param args  Object[]:代理者要替被代理代理的方法的实参列表,可能有的方法不需要
     * @return  Object:代理者要替被代理代理的方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println(target.getClass()+"的"+method.getName()+"方法开始执行");
        long start = System.currentTimeMillis();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //运行实际的具体的业务代码
        //调用被代理者target的method方法
        //使用反射的知识点,这里表示target对象的method方法被调用了
        //method方法代表上面被代理的add,select,fun等之一
       Object returnValue =  method.invoke(target,args);

        long end = System.currentTimeMillis();
        System.out.println("运行时间:"+(end-start)+"毫秒");

        return returnValue;
    }
}

//其他的被代理者
class Other implements  IService{

    @Override
    public void fang() {
        System.out.println("另一个接口的fang");
    }

    @Override
    public void fun() {
        System.out.println("另一个接口的fun");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值