设计原则

单一职责:每个类或每个方法每个框架只做一件事

反例:统计文本文件中有多少个单词

  1. public static void main(String[] args) {
  2. try {
  3. //=========================负责加载路径下的文件
  4. Reader in = new FileReader("F:\\1.txt");
  5. BufferedReader br = new BufferedReader(in);
  6. String line = null;
  7. StringBuilder sb = new StringBuilder("");
  8. while ((line =br.readLine())!=null){
  9. sb.append(line);
  10. sb.append("");
  11. }
  12. //==========================负责根据正则分割字符串
  13. String [] words = sb.toString().split("[^a-zA-Z]+");
  14. System.out.println(words.length);
  15. br.close();
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. }

上面的写法就违反了单一职责,同一个main方法中,我们即让它去加载文件,也让它去做分割,这样做的坏处是,每当我们需要调用其中的一个功能时(如我有文件只需要分割),仍需要重写一遍。
正例

  1. //==============只负责根据路径加载文件
  2. public String loadFile(String path) throws IOException {
  3. Reader in = new FileReader(path);
  4. BufferedReader br = new BufferedReader(in);
  5. String line = null;
  6. StringBuilder sb = new StringBuilder("");
  7. while ((line = br.readLine()) != null) {
  8. sb.append(line);
  9. sb.append("");
  10. }
  11. br.close();
  12. return sb.toString();
  13. }
  14. //=========只负责字符串根据正则分割
  15. public int Textlength(String sb, String regex){
  16. String [] words = sb.split(regex);
  17. return words.length;
  18. }
  19. public static void main(String[] args) throws IOException {
  20. String str = loadFile("F:\\1.txt");
  21. String regex ="[^a-zA-Z]+";
  22. System.out.println(Textlength(str,regex));
  23. }

通过单一职责,可以提高代码的重用性,通过单一职责的方法得到的数据我们不再有耦合,拿来可以做的事也不再局限。

开闭原则:对扩展开放(新功能),对修改关闭(旧功能)

创建了一个汽车对象

  1. class Car {
  2. private String band;
  3. private String color;
  4. private float price;
  5. public String getBand() {
  6. return band;
  7. }
  8. public void setBand(String band) {
  9. this.band = band;
  10. }
  11. public String getColor() {
  12. return color;
  13. }
  14. public void setColor(String color) {
  15. this.color = color;
  16. }
  17. public float getPrice() {
  18. return price;
  19. }
  20. public void setPrice(float price) {
  21. this.price = price;
  22. }
  23. @Override
  24. public String toString() {
  25. return "Car{" +
  26. "band='" + band + '\'' +
  27. ", color='" + color + '\'' +
  28. ", price=" + price +
  29. '}';
  30. }
  31. }
  32. public static void main(String[] args) {
  33. Car car = new Car();
  34. car.setBand("bm");
  35. car.setColor("red");
  36. car.setPrice(13.3f);
  37. System.out.println(car.toString());
  38. }

当变化来临时,例如汽车的价格现在需要打折(打8折),这时,我们在Car的源代码中修改,就违反了开闭原则
反例:修改了源代码

  1. public void setPrice(float price) {
  2. this.price = (price*0.08f);
  3. }

开发时,我们应该要去考虑变化的需求,属性会在任何时刻都有可能产生变化
对待变化时,我们应该选择扩展,而不是去修改源代码(ps:在强制要求符合开闭原则的情况下!)
正例:

  1. //进行扩展
  2. class DiscountCar extends Car{
  3. @Override
  4. public void setPrice(float price) {
  5. super.setPrice(price*0.08f);
  6. }
  7. }
  8. public static void main(String[] args) {
  9. //===使用向上转型时,方法的调用只和new的对象有关
  10. Car car = new DiscountCar();
  11. car.setBand("bm");
  12. car.setColor("red");
  13. car.setPrice(130000f);
  14. System.out.println(car.toString());
  15. }

开闭原则应该遵循应用场景去考虑,如果源代码就是你自己写的,而且需求是稳定的,那么,直接修改源代码也是一个简单的做法,但当源代码是别人的代码或架构是,我们就要去符合开闭原则,防止破坏结构的完整性!

依赖倒置原则:上层不能依赖于下层,它们都应该依赖于抽象

UML类图规则:

关联:一个类的对象,作为另一个类的字段
画法:实线加箭头
class Test{
}
class Test2{
private Test test;
//Test2关联Test
//Test被关联
}
依赖:一个类的方法中定义了另一个类作为局部字段
画法:虚线加箭头
class Test{
}
class Test2{
pubilic void test(){
Test test= new Test;
}
}
继承:画法 实现加空心箭头
实现:画法 虚线加空心箭头

依赖倒置原则:

反例:人喂养动物

  1. static class Person{
  2. public void feed(Dog dog){
  3. dog.eat();
  4. }
  5. }
  6. static class Dog{
  7. public void eat() {
  8. System.out.println("狗啃骨头");
  9. }
  10. }
  11. public static void main(String[] args) {
  12. Person p = new Person();
  13. Dog d = new Dog();
  14. p.feed(d);
  15. }

在上面的代码中,人要喂狗,依赖于有一条狗,人作为上层依赖于下层,这样有什么坏处呢?
坏处是,当变化来临时,比如,人又养了一只猫,那么上层人这个类当中,就必须在添加喂猫的方法,每当下层变动时,上层也会跟着变动,而我们希望下层变动时,上层不会跟着改变
正例:他们都应该依赖于抽象

  1. interface Animal{
  2. void eat();
  3. }
  4. static class Person{
  5. public void feed(Animal animal){
  6. animal.eat();
  7. }
  8. }
  9. static class Dog implements Animal{
  10. public void eat() {
  11. System.out.println("狗啃骨头");
  12. }
  13. }
  14. public static void main(String[] args) {
  15. Person p = new Person();
  16. Dog d = new Dog();
  17. p.feed(d);
  18. }

依赖倒转原则就是指:代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。通过面向接口编程,抽象不应该依赖于细节,细节应该依赖于抽象。

接口隔离原则:设计接口时,接口的抽象应该是有意义的

反例:动物接口中定义的方法并不是被所有动物需要的

  1. interface Animal{
  2. void eat();
  3. void fly();
  4. void swim();
  5. }
  6. class Bird implements Animal{
  7. @Override
  8. public void eat() {
  9. System.out.println("吃");
  10. }
  11. @Override
  12. public void fly() {
  13. System.out.println("飞");
  14. }
  15. //======鸟不会游泳,并不需要实现
  16. @Override
  17. public void swim() {
  18. System.out.println("游泳");
  19. }
  20. }

正例:接口抽象出有意义的层级,供需要的类去实现

  1. interface Flyable{
  2. void fly();
  3. }
  4. interface Swimable{
  5. void swim();
  6. }
  7. interface Eatable{
  8. void eat();
  9. }
  10. class Bird implements Flyable,Eatable{
  11. .....
  12. }
  13. class Dog implements Swimable,Eatable{
  14. .....
  15. }

客户端不应该依赖那些它不需要的接口。
一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可

迪米特法则(最少知道原则):封装,只和朋友通信

什么是朋友?

1.类中的字段
2.方法的返回值
3.方法的参数
4.方法中的实例对象
5.对象本身
6.集合中的泛型

最少知道原则

反例:关闭电脑的流程

  1. class Computers{
  2. public void closeFile(){
  3. System.out.println("关闭文件");
  4. }
  5. public void closeScreen(){
  6. System.out.println("关闭屏幕");
  7. }
  8. public void powerOff(){
  9. System.out.println("断电");
  10. }
  11. }
  12. class Person{
  13. private Computers computers;
  14. public void offComputers(){
  15. computers.closeFile();
  16. computers.closeScreen();
  17. computers.powerOff();
  18. }
  19. }

当用户关闭电脑时,需要调用计算机的各个方法,但是这些方法的细节太多了,会出现用户流程出错,遗漏调用等等,对于用户来言,他只需要知道关机按钮就够了
正例:封装细节,提供接口

  1. class Computers{
  2. public void closeFile(){
  3. System.out.println("关闭文件");
  4. }
  5. public void closeScreen(){
  6. System.out.println("关闭屏幕");
  7. }
  8. public void powerOff(){
  9. System.out.println("断电");
  10. }
  11. public void offComputers(){
  12. closeFile();
  13. closeScreen();
  14. powerOff();
  15. }
  16. }
  17. class Person{
  18. private Computers computers;
  19. public void offComputers(){
  20. computers.offComputers();
  21. }
  22. }

里氏替换原则: 任何能用父类对象的地方,都能透明的使用子类替换

ps:子类替换父类时,不能比父类的访问修饰更严格,不能抛出父类不存在的异常
使用时,需要考虑:
1.是否有is-a的关系
2.有is-a关系后,要考虑子类替换父类后会不会出现逻辑变化
反例

  1. class Counter {
  2. public int add(int i,int j) {
  3. return i+j;
  4. }
  5. }
  6. class soonCounter extends Counter{
  7. @Override
  8. public int add(int i, int j) {
  9. return i-j;
  10. }
  11. }
  12. public static void main(String[] args) {
  13. Counter c = new Counter();
  14. System.out.println(c.add(100,200));
  15. //结果300
  16. Counter c = new soonCounter();
  17. System.out.println(c.add(100,200));
  18. //结果-100
  19. }

我们要知道,在发生向上转型的过程时,方法的调用只和new的对象有关系,所以造成了不同的结果
使用里氏替换原则时,要知道 子类可以扩展父类的功能,但不能改变父类原有的功能

组合优于继承:复用别人代码时,应该使用组合

反例:继承hashset,重写父类add方法,每次add元素时count+1;

  1. class Counter extends HashSet {
  2. private int count =0;
  3. public boolean add(int i) {
  4. count++;
  5. return super.add(i);
  6. }
  7. public int getCount(){
  8. return count;
  9. }
  10. }
  11. public static void main(String[] args) {
  12. Counter c = new Counter();
  13. c.add(1);
  14. c.add(2);
  15. c.add(3);
  16. System.out.println(c.getCount());
  17. }

这样写会出现在hashset方法中,不只是add可以添加元素的问题,还有其他方式可以添加,如果是其他方式添加的,count就不会累加,这样是有问题的

  1. @Override
  2. public boolean addAll(Collection c) {
  3. count=count+c.size();
  4. return super.addAll(c);
  5. }

假如我们继续重写addAll方法添加相应的判断时,又会出现新的问题,我们的count并没有正确的累计,因为在HashSet的源码addAll方法中,回调了add方法,并没有解决需求
那我们不重写addAll,反正它会回调add,完成计数,就没有问题了吗?
其实并不能解决问题,hashset的源码我们不能保证永远不会更改,假如在下一个版本中,hashset的作者更改了addAll方法,那么我们的功能也会不能正常实现了!
当继承的父类作者不是我们自己的时候,我们没有办法保证父类代码不会变更,假如我们继承了这个父类,那么我们最好是只去复用父类的代码,避免去重写或新建方法,防止源码结构变更带来的打击
也就是说,在我们需要重用代码,并且重用的代码作者并不是我们自己的时候,我们要采用组合的方式。
正例:组合优于继承

  1. static class Counter {
  2. HashSet hashSet = new HashSet();
  3. private int count =0;
  4. public boolean add(int i) {
  5. count++;
  6. return hashSet.add(i);
  7. }
  8. public boolean addAll(Collection c) {
  9. count=count+c.size();
  10. return hashSet.addAll(c);
  11. }
  12. public int getCount(){
  13. return count;
  14. }
  15. }
  16. public static void main(String[] args) {
  17. Counter c = new Counter();
  18. c.add(1);
  19. c.add(2);
  20. c.add(3);
  21. System.out.println(c.getCount());
  22. }

这样来写,我们类中的add和addAll方法跟HashSet中的add和addAll方法的不在有关系,也能解决这个问题

设计模式

工厂模式:提供了一种创建对象的最佳方式

简单工厂模式

正例:创建产品接口,具体产品实现,工厂类负责生产,使用时找到工厂类拿到对应产品

  1. class Test {
  2. interface Food {
  3. void eat();
  4. }
  5. static class Bun implements Food {
  6. @Override
  7. public void eat() {
  8. System.out.println("包子");
  9. }
  10. }
  11. static class SteameDread implements Food {
  12. @Override
  13. public void eat() {
  14. System.out.println("馒头");
  15. }
  16. }
  17. static class FoodFactory {
  18. public Food getFood(int n) {
  19. Food food = null;
  20. switch (n) {
  21. case 1:
  22. food = new Bun();
  23. break;
  24. case 2:
  25. food = new SteameDread();
  26. break;
  27. }
  28. return food;
  29. }
  30. }
  31. public static void main(String[] args) {
  32. FoodFactory foodFactory = new FoodFactory();
  33. foodFactory.getFood(1).eat();
  34. }
  35. }

工厂模式

正例:为了进行扩展,不违反开闭原则

class Test {
    interface Food {
        void eat();
    }
    static class Bun implements Food {
        @Override
        public void eat() {
            System.out.println("包子");
        }
    }
    static class SteameDread implements Food {
        @Override
        public void eat() {
            System.out.println("馒头");
        }
    }
    static interface FoodFactory{
        Food getFood();
    }
    static class BunFactory implements FoodFactory{

        @Override
        public Food getFood() {
            return new Bun();
        }
    }
    static class SteameDreadFactory implements FoodFactory{

        @Override
        public Food getFood() {
            return new SteameDread();
        }
    }
    static class Business {
        public void test(FoodFactory foodFactory){
            Food food = foodFactory.getFood();
            food.eat();
        }
    }
    public static void main(String[] args) {
        Business business = new  Business();
        business.test(new SteameDreadFactory());
        //business.test(new BunFactory());
    }
}

把工厂接口化,抽象产品和接口工厂。
具体产品实现抽象产品,具体工厂实现接口工厂
一个业务类
通过调用业务类的方法,将具体工厂传入就能道道具体产品
优点:在不改变源代码的情况下,可以方便的进行扩展,比如突然增加了产品:面条。我们不需要去改写源代码,直接创建具体产品类实现抽象产品,在创建具体工厂类实现接口工厂,就可以通过调用业务的方法来得到我们新增加的产品了,符合了开闭原则。
缺点:当业务需要的类型变多,目前只有食物,当产生饮料,日用品等类别时,我们又要创建新的工厂来实现,造成代码重复的坏味道。

抽象工厂模式

变化来了,工厂模式固然很好用,但我们现在要求不光可以生产食物,还可以生产饮料,这时,按照工厂模式我们就不得不去创建抽象的饮料工厂,造成新增类别产品就要新增接口工厂的问题。
正例:为了解决这个问题,我们直接将工厂抽象出来,工厂可以生产不同的产品

class Test {
    interface Food {
        void eat();
    }
    interface Drink {
        void drink();
    }
    static class Bun implements Food {
        @Override
        public void eat() {
            System.out.println("包子");
        }
    }
    static class SteameDread implements Food {
        @Override
        public void eat() {
            System.out.println("馒头");
        }
    }
    static class CoCo implements Drink {
        @Override
        public void drink() {
            System.out.println("奶茶");
        }
    }
    static class Tea implements Drink {
        @Override
        public void drink() {
            System.out.println("茶");
        }
    }
    static interface Factory{
        Food getFood();
        Drink getDrink();
    }
    static class BunFactoryAndCoCo implements Factory{

        @Override
        public Food getFood() {
            return new Bun();
        }

        @Override
        public Drink getDrink() {
            return new CoCo();
        }
    }
    static class SteameDreadAndTeaFactory implements Factory{

        @Override
        public Food getFood() {
            return new SteameDread();
        }

        @Override
        public Drink getDrink() {
            return new Tea();
        }
    }
    static class Business {
        public void test(Factory factory){
            Food food = factory.getFood();
            food.eat();
            Drink drink =factory.getDrink();
            drink.drink();
        }
    }
    public static void main(String[] args) {
        Business business = new  Business();
        business.test(new SteameDreadFactory());
        //business.test(new BunFactory());
    }

抽象工厂中,我们把原本的接口工厂,抽象化,使其可以生产多个产品,具体的产品工厂去实现这个工厂,重写他的方法返回需要的产品,业务中,我们只需要依赖抽象工厂,返回抽象工厂的具体实现就可以,这样,调用是我们就只需要调用业务的方法,传入我们需要的具体产品工厂就可以了。

建造者模式:构建与它的表示分离

正例:

class Computer {
    String Cpu;
    String Gpu;
    String Hd;
    get//set
}

interface ComputerBuilder {
    void setCpu();
    void setGpu();
    void setHd();
    Computer getComputer();
}

class lowComputer implements ComputerBuilder {
    private Computer computer = new Computer();
    @Override
    public void setCpu() {
        computer.setCpu("i3");
    }
    @Override
    public void setGpu() {
        computer.setGpu("hd4000");
    }
    @Override
    public void setHd() {
        computer.setHd("机械500");
    }
    @Override
    public Computer getComputer() {
        return computer;
    }
}

class MediumComputer implements ComputerBuilder {
    private Computer computer = new Computer();

    @Override
    中配重写
}

class AdvancedComputer implements ComputerBuilder {
    private Computer computer = new Computer();

    @Override
    高配重写
}
//指挥者负责调用哪一个建造者
class Commander {
    private ComputerBuilder computerBuilder;
    public Commander(ComputerBuilder computerBuilder) {
        this.computerBuilder = computerBuilder;
    }
    public Computer builder(){
        computerBuilder.setCpu();
        computerBuilder.setGpu();
        computerBuilder.setHd();
        return computerBuilder.getComputer();
    }
}
class AppTest{
    public static void main(String[] args) {
        lowComputer lowComputer = new lowComputer();
        MediumComputer mediumComputer = new MediumComputer();
        AdvancedComputer advancedComputer = new AdvancedComputer();
        Commander commander = new Commander(advancedComputer);
        System.out.println(commander.builder().toString());
    }
}

适配器模式:根据已有接口,生成想要的接口

正例:

class Util{
    public static void walk(Iterator iterator){
        while (iterator.hasNext()){
            Object o= iterator.next();
            System.out.println(o);
        }
    }
}
class EnumerationAdapter implements Iterator{
    private Enumeration enumeration;

    public EnumerationAdapter(Enumeration enumeration) {
        this.enumeration = enumeration;
    }

    @Override
    public boolean hasNext() {
        return enumeration.hasMoreElements();
    }

    @Override
    public Object next() {
        return enumeration.nextElement();
    }
}
class Test {
    public static void main(String[] args) {
        List  list = new ArrayList(Arrays.asList(1,2,3,4,5));
        Set set = new HashSet(Arrays.asList(10,20,20,30,40));
        Util.walk(list.iterator());
        Util.walk(set.iterator());
        Enumeration enumeration = new StringTokenizer("haha,heihei,xixi,zizi");
        EnumerationAdapter e = new EnumerationAdapter(enumeration);
        Util.walk(e);
    }

我们拥有一个工具类Util,可以传入Iterator将其中的元素迭代展示出来,但是Enumeration这个数据结构不能放入我们的工具类中,我们通过适配器EnumerationAdapter,将Enumeration关联,在new时通过构造器中拿到它的值,通过实现Iterator,实现其中的方法,因为EnumerationAdapter实现了Iterator,就可以被我们的工具类Util使用了。

装饰器模式:允许向一个现有的对象添加新的功能,同时又不改变其结构

反例:我们有一个奶茶店,但是该店并不是所有的饮料都是一个名字和一个价钱,所有我们用了以下的写法,将变化的价格设为抽象的,因为每个饮料都有名字,我们将他设为构造器中生成

class Test {
    static abstract class CoCo{
        private String name;

        public CoCo(String name){
                this.name=name;
        }
        abstract int price();

        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    static class MilkyTea extends CoCo{
        public MilkyTea() {
            super("奶茶:");
        }
        @Override
        int price() {
            return 2;
        }
    }
    static class Tea extends CoCo{
        public Tea() {
            super("茶:");
        }
        @Override
        int price() {
            return 1;
        }
    }
    public static void main(String[] args) {
        CoCo coCo = new MilkyTea();
        System.out.println(coCo.getName()+coCo.price());
    }
}

变化来了,coco奶茶店突然要为每个饮品都添加调料来进行销售,此时,我们如果为每个饮品单独添加调料属性的话,(创建新的类)会出现很多种组合的情况,且调料种类越多组合越多,导致出现类爆炸的情况
而如果我们去在父类里去添加对应的调料字段,在子类中分别判断时,则又违反了开闭原则
这时,使用装饰者模式
正例:

class Test {
    static abstract class CoCo{
        private String name;

        public String getName() {
            return name;
        }

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

        public CoCo(String name){
                this.name=name;
        }
        abstract int price();
    }
    static abstract class Condiments extends CoCo{
        protected CoCo CoCo;
        public Condiments(CoCo coCo) {
            super("调味料");
            this.CoCo=coCo;
        }
    }
    static class Pearl extends Condiments{
        public Pearl(CoCo name) {
            super(name);
        }
        @Override
        int price() {
            return CoCo.price()+1;
        }
        @Override
        public String getName() {
            return CoCo.getName()+"珍珠";
        }
    }
    static class Mesona extends Condiments{
        public Mesona(CoCo name) {
            super(name);
        }
        @Override
        int price() {
            return CoCo.price()+2;
        }
        @Override
        public String getName() {
            return CoCo.getName()+"仙草";
        }
    }
    static class MilkyTea extends CoCo{
        public MilkyTea() {
            super("奶茶:");
        }

        @Override
        int price() {
            return 2;
        }
    }
    static class Tea extends CoCo{
        public Tea() {
            super("茶:");
        }
        @Override
        int price() {
            return 1;
        }
    }

    public static void main(String[] args) {
        CoCo coCo = new MilkyTea();
        Pearl pearlMilkyTea = new Pearl(coCo);
        Mesona mesona = new Mesona(pearlMilkyTea);
        System.out.println(mesona.getName()+mesona.price());
    }
}

将调料类抽象继承并且CoCo,构造方法中,将父类作为参数设置关联值,如果有新的调料,例如珍珠,就继承调料类,重写调料类父类的两个方法,改变价格和名字
在使用时,我们通过向上转型得到具体饮料,添加调味料时,将饮料放入构造中,这样珍珠类的构造就设置好了饮料的值,并重写方法加以修改,得到我们想要的效果
装饰者模式另一个例子
通过装饰的方式把接口变成我们想要的接口,将原来只能读字节的Reader变成了读整行的功能。

class Test extends Reader {
    private Reader in;
    public Test(Reader in) {
        this.in = in;
    }
    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        return 0;
    }
    public String readLine() throws IOException {
        StringBuilder str = new StringBuilder();
        while (true) {
            int n = in.read();
            if(n=='\n'){
                break;
            }
            str.append((char) n);
        }
        return str.toString();
    }
    @Override
    public void close() throws IOException {
        in.close();
    }
}
class AppTest {
    public static void main(String[] args) throws IOException {
        Reader reader = new FileReader("F:\\1.txt");
        Test t = new Test(reader);
        System.out.println(t.readLine());
    }
}

模板设计模式:基于抽象的封装

正例:将不会改变的地方变为模板,而会发生改变的部分代码变成抽象的,这样调用它的人负责实现,来实现它需要的效果

abstract class Test{
        public void  templent(){
            long start = System.currentTimeMillis();
            code();
            long end = System.currentTimeMillis();
            System.out.println(end-start);
        }
        abstract void code();
    }
class AppTest{
    static class ArrayTest extends Test{
        @Override
        void code() {
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                list.add(0,1);
            }
        }
    }
    public static void main(String[] args) {
        Test test= new ArrayTest();
        test.templent();
    }
}

策略模式:一个类的行为可以在运行时更改

我们知道鸭子这个动物,每个鸭子都会吃和叫,但是鸭子们的外观都长得不一样,我们就设计了一个类
鸭子类

static abstract class Duck{
        public void eat(){
            System.out.println("吃");
        }
        public void call(){
            System.out.println("叫");
        }
        abstract void exterior();
    }

这样的类,就完成了我们想要的效果,不同的鸭子只要继承Duck,实现一下不一样的外观方法就可以了
但是,问题来了,现在我们知道,有的鸭子是会飞的,所有,我们需要给鸭子来添加飞的方法,但是要添加在哪里呢?

反例:添加给父类,造成的问题是如下,并不是所有的鸭子都会飞,橡皮鸭不会飞,我们要重写飞的方法来解决这个问题

class Test{
    static abstract class Duck{
        public void eat(){
            System.out.println("吃");
        }
        public void call(){
            System.out.println("叫");
        }
        public void fly(){
            System.out.println("飞");
        }
        abstract void exterior();
    }
    class RedDuck extends Duck{
        @Override
        void exterior() {
            System.out.println("红头");
        }
    }
    class BlueDuck extends Duck{
        @Override
        void exterior() {
            System.out.println("蓝头");
        }
    }
    static class RubberDuck extends Duck{
        @Override
        public void fly() {
            System.out.println("没飞起来");
        }
        @Override
        void exterior() {
            System.out.println("橡皮");
        }
    }

    public static void main(String[] args) {
        Duck r = new RubberDuck();
        r.exterior();
        r.eat();
        r.call();
        r.fly();
    }
}

但是,这样写下去的话,我们每个不会飞的鸭子,都要去重写一下飞的方法,一旦变多,那就是程序员的噩梦

反例:我们想到了接口,采用接口完成,好了一些,但却仍然要不断地判断鸭子的状态需要实现哪些接口,并且方法没有重用性可言,每个实现接口的方法都要去完成方法体

class Test{
    static abstract class Duck{
        public void eat(){
            System.out.println("吃");
        }
        abstract void exterior();
    }
    interface CallAble{
        void call();
    }
    interface FlyAble{
        void fly();
    }
    class RedDuck extends Duck implements CallAble,FlyAble{
        @Override
        void exterior() {
            System.out.println("红头");
        }

        @Override
        public void call() {
            System.out.println("叫");
        }

        @Override
        public void fly() {
            System.out.println("飞");
        }
    }
    class BlueDuck extends Duck implements CallAble,FlyAble{
        @Override
        public void call() {
            System.out.println("叫");
        }
        @Override
        public void fly() {
            System.out.println("飞");
        }
        @Override
        void exterior() {
            System.out.println("蓝头");
        }
    }
    static class RubberDuck extends Duck implements CallAble{
        @Override
        public void call() {
            System.out.println("吱吱叫");
        }
        @Override
        void exterior() {
            System.out.println("橡皮");
        }
    }

    public static void main(String[] args) {
        RubberDuck r = new RubberDuck();
        r.exterior();
        r.eat();
        r.fly();
        r.call();
    }
}

当然,jdk1.8中,接口可以添加默认实现

    interface FlyAble{
       default void fly(){
           System.out.println("飞");
       };
    }

但这样还是没有解决根本问题,我们还是在重复判断鸭子状态的问题,去区分是否实现哪些接口,并要考虑是不是要去重写
正例:使用策略模式

class Test{
    interface FlyBehavior{
        void fly();
    };
    interface CallBehavior{
        void call();
    }
    static class FlyWithWings implements FlyBehavior{
        @Override
        public void fly() {
            System.out.println("用翅膀飞");
        }
    }
    static class FlyWithKick implements FlyBehavior{
        @Override
        public void fly() {
            System.out.println("踢飞");
        }
    }
    static class Quack implements CallBehavior{
        @Override
        public void call() {
            System.out.println("嘎嘎叫");
        }
    }
    static class Squeak implements CallBehavior{
        @Override
        public void call() {
            System.out.println("吱吱叫");
        }
    }
    static abstract class Duck{
        protected FlyBehavior flyBehavior;
        protected CallBehavior callBehavior;
        public void  performFly(){
            flyBehavior.fly();
        }
        public void  performCall(){
            callBehavior.call();
        }
        public void eat(){
            System.out.println("会吃");
        }
        abstract void exterior();
    }
    static class RedDuck extends Duck{
        public RedDuck() {
            this.flyBehavior = new FlyWithWings();
            this.callBehavior = new Quack();
        }
        @Override
        void exterior() {
            System.out.println("红头鸭");
        }
    }
    class BlueDuck extends Duck{
        public BlueDuck() {
            this.flyBehavior = new FlyWithWings();
            this.callBehavior = new Quack();
        }
        @Override
        void exterior() {
            System.out.println("蓝头鸭");
        }
    }
    static class RubberDuck extends Duck{
        public RubberDuck() {
            this.flyBehavior = new FlyWithKick();
            this.callBehavior = new Squeak();
        }
        @Override
        void exterior() {
            System.out.println("橡皮鸭");
        }
    }

    public static void main(String[] args) {
        RubberDuck r = new RubberDuck();
        r.exterior();
        r.eat();
        r.performFly();
        r.performCall();
        RedDuck r2 = new RedDuck();
        r2.exterior();
        r2.eat();
        r2.performFly();
        r2.performCall();
    }
}

我们用不同的模式去实现不同的行为,符合开闭原则的同时,我们使用时,通过父类去关联接口,子类构造器将接口判断行为分别实例化,这样就提高了不同行为的重用性,并且后续运行时,我们可以去通过set方法去实时的改变状态
运行时可以随时通过set方法改变状态,如下:

 r.setFlyBehavior(new FlyWithWings());
 r.performFly();

代理模式:为其他对象提供一种代理以控制对这个对象的访问

一个简单的加减乘除功能

class Test{
    interface Calculator{
        int add(int a,int b);
        int sub(int a,int b);
        int mul(int a,int b);
        int div(int a,int b);
    }
    static class  CalImpl implements Calculator{
        @Override
        public int add(int a, int b) {
            return a+b;
        }

        @Override
        public int sub(int a, int b) {
            return a-b;
        }

        @Override
        public int mul(int a, int b) {
            return a*b;
        }

        @Override
        public int div(int a, int b) {
            return a/b;
        }
    }

    public static void main(String[] args) {
        CalImpl c = new CalImpl();
        System.out.println(c.add(4,2));
        System.out.println(c.sub(4,2));
        System.out.println(c.mul(4,2));
        System.out.println(c.div(4,2));
    }
}

变化来了,要求为每个方法调用前后,都需要打印出一个日志信息

class Test{
    interface Calculator{
        int add(int a,int b);
        int sub(int a,int b);
        int mul(int a,int b);
        int div(int a,int b);
    }
    static class  CalImpl implements Calculator{
        @Override
        public int add(int a, int b) {
            System.out.println("add方法开始!" +"a="+a+"b="+b);
            int r = a+b;
            System.out.println("add方法结束!" +"r="+r);
            return r;
        }

        @Override
        public int sub(int a, int b) {
            System.out.println("sub方法开始!" +"a="+a+"b="+b);
            int r = a-b;
            System.out.println("sub方法结束!" +"r="+r);
            return r;
        }

        @Override
        public int mul(int a, int b) {
            System.out.println("mul方法开始!" +"a="+a+"b="+b);
            int r = a*b;
            System.out.println("mul方法结束!" +"r="+r);
            return r;
        }

        @Override
        public int div(int a, int b) {
            System.out.println("div方法开始!" +"a="+a+"b="+b);
            int r = a/b;
            System.out.println("div方法结束!" +"r="+r);
            return r;
        }
    }

    public static void main(String[] args) {
        CalImpl c = new CalImpl();
        System.out.println(c.add(4,2));
        System.out.println(c.sub(4,2));
        System.out.println(c.mul(4,2));
        System.out.println(c.div(4,2));
    }
}

我们发现,这样完成业务根本不是一个好办法,代码在重复,业务(加减)和非核心业务(打印日志)在不断的重复,需求如果变化要加入开方,求余的过程,或者,需要上午需要日志,下午不需要日志的需求,那我们就需要不断地改写这段代码
我们应该使用动态代理来完成

动态代理:在内存中写入的字节码,直接被类加载器使用

微信图片_20200520154434.png

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

interface Calculator {
    int add(int a, int b);

    int sub(int a, int b);

    int mul(int a, int b);

    int div(int a, int b);
}
class CalImpl implements Calculator {
    @Override
    public int add(int a, int b) {
        int r = a + b;
        return r;
    }

    @Override
    public int sub(int a, int b) {
        int r = a - b;
        return r;
    }

    @Override
    public int mul(int a, int b) {
        int r = a * b;
        return r;
    }

    @Override
    public int div(int a, int b) {
        int r = a / b;
        return r;
    }
}
class MyHandler implements InvocationHandler {
    private Calculator calculator;
    public MyHandler(Calculator calculator) {
        this.calculator = calculator;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName()+"开始,参数:"+ Arrays.toString(args));
        System.out.println(method.getName()+"结束,结果是:"+method.invoke(calculator,args));
        return 0;
    }
}
public class AppTest {
    public static void main(String[] args) {
        Calculator calculator = new CalImpl();
        ClassLoader classLoader = AppTest.class.getClassLoader();
        //创建代理对象
        Calculator cal = (Calculator) Proxy.newProxyInstance(classLoader, new Class[]{Calculator.class}, new MyHandler(calculator));
        cal.add(3, 2);
        cal.sub(3, 2);
        cal.mul(3, 2);
        cal.div(3, 2);
    }
}

动态代理api
Proxy.newProxyInstance(classLoader, new Class[]{Calculator.class}, new MyHandler(calculator)
第一个参数:实例化一个对象,必然会调用类的构造器,运行第一次调用构造器,必定导致类的加载,而加载类的时候,就是jvm拿着classloader去加载类的字节码的,把字节码加载到内存中,才能进一步实例化对象
简单来说:只要实例化的对象,一定要加载类的字节码,加载字节码就一定要类的加载器。
使用动态代理的api实例化对象是一种不常用的方式,但这也是一种实例化,需要我们手动把类的加载器传入
第二个参数:实例化某一个类的对象,那是哪个类呢?这个类是在内存中,由动态代理生成的,这个对象会自动实现参数2的指定接口,生成的对象,必定能转成指定的接口类型。拥有接口中的方法
第三个参数:每次对动态代理对象方法的调用,都是一个假对象,虽然实现了接口方法,但没有任何内容,所以,调用方法会进入第三个参数的invoke方法中,第三个参数,是interfacehadle,通过类实现这个接口重写下面的方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
第一个参数:动态代理的对象
第二个参数:调用的接口方法
第三个参数:调用的接口方法的参数
}
动态代理具体步骤

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

    观察者模式:被观察者发生变化时,所有被观察者都会通知

    如题,我们先抽象出被观察者和观察者
    /**
    *抽象观察者
    */
    abstract class obServer {
     protected String name;
     public obServer(String name) {
         this.name = name;
     }
     protected SubJect subJect;
     //更新接口
     public abstract void updata();
     //不在观察
     public abstract void dontLook(SubJect subJect);
    }
    
/**
 *抽象被观察者
 */
abstract class SubJect{
    //所有观察者
    private List<obServer> obServerList=new ArrayList<>();
    //状态
    private int state;
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
    }
    //添加观察者
    public void attach(obServer obServer){
        obServerList.add(obServer);
        System.out.println("新增观察者:"+obServer.name);
    }
    //删除观察者
    public void deletObServer(obServer obServer){
        obServerList.remove(obServer);
        System.out.println("移除观察者:"+obServer.name);
    }
    //更新观察者
    public void updateObServer(){
        for (obServer o:obServerList) {
            o.updata();
        }
    }
}

有一档电视节目来当实体(被观察者)

class TVPlay extends SubJect{
    private String theTVSet;
    public String getTheTVSet() {
        return theTVSet;
    }
    public void setTheTVSet(String theTVSet) {
        this.theTVSet = theTVSet;
    }
    public void play(){
        System.out.println("电视剧开播了!今日节目:"+ theTVSet);
        //通知观察者
        updateObServer();
    }
}

人实体来充当观察者

class People extends obServer{
    public People(String name) {
        super(name);
    }
    @Override
    public void updata() {
        System.out.println(name+"收到电视剧开播通知");
    }

    @Override
    public void dontLook(SubJect subJect) {
        subJect.deletObServer(this);
    }
}

执行时,被观察者会检查所有观察者,去调用play下发通知

 public static void main(String[] args) {
        TVPlay tvPlay = new TVPlay();
        People man = new People("男人");
        People woman = new People("女人");
        tvPlay.attach(man);
        tvPlay.attach(woman);
        tvPlay.setTheTVSet("宫锁心玉");
        tvPlay.play();

        System.out.println("新的一期开始了!");
        System.out.println("上次不好看,男人取消了观察");
        man.dontLook(tvPlay);
        tvPlay.setTheTVSet("宫锁心玉2");
        tvPlay.play();
    }

执行结果
image.png