面向对象七大设计原则,看了必会(代码详细版)(中)

简介: 面向对象七大设计原则,看了必会(代码详细版)(中)

正确示范

public class OpenClosed2 {
    public static void main(String[] args) {
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());//+新增绘制三角形
    }
}
//这是一个用于绘图的类
class GraphicEditor {
    //接收 Shape 对象,调用 draw 方法
    public void drawShape(Shape s) {
        s.draw();
    }
}
abstract class Shape {
    int m_type;
    public abstract void draw();
}
//以前就写好的
class Rectangle extends Shape {
    Rectangle() {
        super.m_type = 1;
    }
    @Override
    public void draw() {
        System.out.println("绘制矩形");
    }
}
//以前就写好的
class Circle extends Shape {
    Circle() {
        super.m_type = 2;
    }
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}
//+新增绘制三角形
class Triangle extends Shape {
    Triangle() {
        super.m_type = 3;
    }
    @Override
    public void draw() {
        System.out.println("绘制三角形");
    }
}
绘制矩形
绘制圆形
绘制三角形

3、里氏替换原则

定义

子类可以扩展父类的功能,但不能改变原有父类的功能。

详细描述

里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。

根据上述理解,对里氏替换原则的定义可以总结如下:

● 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法

● 子类中可以增加自己特有的方法

● 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松

● 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等

子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法

枪支抽象类:
public abstract class AbstractGun {
    public abstract  void shoot();
}
手枪实现类:
public class HandGun extends AbstractGun {
    public void shoot() {
       System.out.println("手机射击");     
   }
}
public class Rifle extends AbstractGun {
    public void shoot() {
       System.out.println("步枪射击");     
   }
}
士兵实现类:
public class Soldier {
  private AbstractGun gun;
  public void setGun(AbstractGun gun) {
    this.gun = gun;
  }
  public void killEnemy() {
    System.out.println("士兵杀敌人");
    gun.shoot();
  }
}
场景类:
  public class Client {
    public static void main(String[] args) {
      Soldier sanMao = new Soldier();
      sanMao.setGun(new Rifle());
      sanMao.killEnemy();
  }
}

注意

在类中调用其他类时务必要使用父类或者接口(例如Solider类的setGun(AbstractGun gun)方法),否则说明类的设计已经违背了LSP原则。

现在有个玩具枪该怎么定义?直接继承AbstractGun类吗?如下:

public class ToyGun extends AbstractGun {
  @Override
  public void shoot() {
    //玩具枪不能像真枪杀敌,不实现
  }
}
场景类:
  public class Client {
    public static void main(String[] args) {
      Soldier sanMao = new Soldier();
      sanMao.setGun(new ToyGun());
      sanMao.killEnemy();
  }
}

在这种情况下,士兵拿着玩具枪杀敌,发现业务调用类已经出现了问题,正常的业务逻辑运行结果是不正确的。(因为玩具枪并不能杀敌)ToyGun应该脱离继承,建立一个独立的类,可以与AbstractGun建立关联委托关系。类图如下:

注意

如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中发生重写或者重载,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。

2、子类中可以增加自己特有的方法

说下面两层含义之前先要明白 重载 重写(覆盖) 的区别:

重写(覆盖)的规则:

1、重写方法的参数列表必须完全与被重写的方法的相同,否则不能称其为重写而是重载.

2、重写方法的访问修饰符一定要大于被重写方法的访问修饰符(public>protected>default>private)。

3、重写的方法的返回值必须和被重写的方法的返回一致;

4、重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类;

5、被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写。

6、静态方法不能被重写为非静态的方法(会编译出错)。

重载的规则:

1、在使用重载时只能通过相同的方法名、不同的参数形式实现。不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不一样);

2、不能通过访问权限、返回类型、抛出的异常进行重载;

3、方法的异常类型和数目不会对重载造成影响;

3、当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松

public class Father {
    public Collection doSomething(HashMap  map){
        System.out.println("父类被执行了");
        return map.values();
    }
}
public class Son  extends Father{
    public Collection doSomething(Map  map){
        System.out.println("子类被执行了");
        return map.values();
    }
}
public class Client{
    public static void main(String[] args) {
        invoker();
    }
    public  static void invoker(){
           Son son = new  Son();//子类对象
           HashMap  map=new HashMap<>();
           son.doSomething(map);
    }
}

运行是”父类被执行了”,这是正确的,父类方法的参数是HashMap类型,而子类的方法参数是Map类型,子类的参数类型范围比父类大,那么子类的方法永远也不会执行。

如果我们反过来让父类的参数类型范围大于子类,并在调用时用子类去调用,我们会发现打印时的结果是”子类被执行了”,这就违反了里氏替换原则,在开发中很容易引起业务逻辑的混乱,所以类的方法重载父类的方法时,方法的前置条件(形参)要比父类方法的输入参数更宽松(相同也可以)。

4、当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等

父类的一个方法的返回值是一个类型T,子类的相同方法(重载或者重写)的返回值为S,那么里氏替换原则就要求S必须小于等于T。

4、接口隔离

原则介绍

客户端不应该依赖它不需要的接口, 即一个类对另一个类的依赖应该建立在最小的接口上。

示范

//A、B总的接口
interface InterfaceAll {
    void operation1();
    void operation2();
    void operation3();
}
//实现类A只用InterfaceAll中的operation1、operation2方法,所以实现两个方法
class A implements InterfaceAll {
    @Override
    public void operation1() {
        System.out.println("A 实现了 operation1...");
    }
    @Override
    public void operation2() {
        System.out.println("A 实现了 operation2...");
    }
    @Override
    public void operation3() {
        //A用不到,但是还需要空实现
    }
}
//实现类B只用InterfaceAll中的operation1、operation3方法,所以实现两个方法
class B implements InterfaceAll {
    @Override
    public void operation1() {
        System.out.println("B 实现了 operation1...");
    }
@Override
    public void operation2() {
        //B用不到,但是还需要空实现
    }
    @Override
    public void operation3() {
        System.out.println("B 实现了 operation3...");
    }
}
正确示范
//接口Interface1
interface Interface1 {
    void operation1();
}
//接口Interface2
interface Interface2 {
    void operation2();
}
//接口Interface3
interface Interface3 {
    void operation3();
}
//实现类A用Interface1中的operation1和Interface2中的operation2
class A implements Interface1, Interface2 {
    @Override
    public void operation1() {
        System.out.println("A 实现了 operation1...");
    }
    @Override
    public void operation2() {
        System.out.println("A 实现了 operation2...");
    }
}
//实现类B用Interface1中的operation1和Interface3中的operation3
class B implements Interface1, Interface3 {
    @Override
    public void operation1() {
        System.out.println("B 实现了 operation1...");
    }
    @Override
    public void operation3() {
        System.out.println("B 实现了 operation3...");
    }
}


相关文章
|
设计模式 算法 安全
一文带你通俗理解23种软件设计模式(推荐收藏,适合小白学习,附带C++例程完整源码)
一文带你通俗理解23种软件设计模式(推荐收藏,适合小白学习,附带C++例程完整源码)
2556 0
|
消息中间件 监控 算法
高效排队,紧急响应:RabbitMQ Priority Queue全面指南【RabbitMQ 九】
高效排队,紧急响应:RabbitMQ Priority Queue全面指南【RabbitMQ 九】
684 0
|
10月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
396 0
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
小程序 Java 开发工具
【Java】@Transactional事务套着ReentrantLock锁,锁竟然失效超卖了
本文通过一个生动的例子,探讨了Java中加锁仍可能出现超卖问题的原因及解决方案。作者“JavaDog程序狗”通过模拟空调租赁场景,详细解析了超卖现象及其背后的多线程并发问题。文章介绍了四种解决超卖的方法:乐观锁、悲观锁、分布式锁以及代码级锁,并重点讨论了ReentrantLock的使用。此外,还分析了事务套锁失效的原因及解决办法,强调了事务边界的重要性。
515 2
【Java】@Transactional事务套着ReentrantLock锁,锁竟然失效超卖了
|
设计模式 前端开发 JavaScript
深入探索研究MVVM架构设计
【10月更文挑战第7天】
741 0
什么是依赖倒置原则
依赖倒置原则(Dependency Inversion Principle, DIP)是面向对象设计中的SOLID原则之一,强调高层模块不应依赖低层模块,双方应依赖于抽象。该原则包含两方面:抽象不依赖细节,细节依赖抽象。这有助于降低耦合度、提高模块化和灵活性。实践中可通过接口定义契约、依赖注入等方式实现。例如,在Java中定义`MessageService`接口及其实现`EmailService`,高层`NotificationService`依赖于`MessageService`接口而非具体实现,从而实现了对扩展开放、对修改关闭的设计目标。
809 1
|
C语言
以c语言为基础实现的简易扫雷游戏(游戏代码附在文章最后,如有需要请自取)
以c语言为基础实现的简易扫雷游戏(游戏代码附在文章最后,如有需要请自取)
642 1
|
关系型数据库
面向对象七大设计原则,看了必会(代码详细版)(上)
面向对象七大设计原则,看了必会(代码详细版)(上)
|
存储 算法 Java
22个常用数据结构实现与原理分析
前两天V哥跟一个老学员吃饭,聊起面试大厂的事,说为啥大厂面试第一看基本条件,第二就是考数据结构算法,其他高阶的内容会比较少,最近V哥也在跟大厂对接这一块业务,了解得多一些,这是因为考察基本功能力被放到了重要位置,大厂认为硬性条件,比如学历过关,基本功够扎实,那对于实际工作用的上层技能,内部培养就好,也就是相比你掌握了多少多少牛逼的高阶技术,他们更在乎你的基本功,所以,进大厂,基本功必须要搞稳,否则白扯,今天 V 哥把总结好的22个常用的数据结构实现原理,和示例分析分享给大家,希望对你有帮助,觉得内容有收获,请帮忙转发给更多需求的朋友,共同进步。
282 0

热门文章

最新文章