面向对象与设计原则

  • 三大特性
  • 抽象类和接口
      • 抽象类的三个特性(abstract)
      • 接口的三个属性(interface)
      • 抽象类和接口的区别
      • 两者再区别
      • 总结
  • 基于接口而非实现编程
  • SOLID原则
三大特性
  • 封装:封装也叫做信息隐匿或者数据访问保护,类通过暴露有限的访问接口,授权外部只能通过有限的方式来访问内部信息或者数据。在java语言中主要是通过public、protected、private等关键字实现的。
  • 继承:是用来表示类之前的is-a关系,比如猫是一种哺乳动物。从继承关系上来讲,继承可以分为单继承和多继承,多继承就是,例如猫既可以是一种哺乳动物,也可以是一种爬行动物。继承最大的优点就是可以实现代码复用,即,可以把子类都具有的属性或者方法提取到父类当中。
  • 多态:子类可以替代父类,类如Map map = new HashMap(),多态提高代码的扩展性和复用性。如下,whatyousay方法中可以根据传入的不同的对象输出不容的内容。
    interface ProgrammingLanguage{ public void say(); }class JavaLanguage implements ProgrammingLanguage{ public void say(){ System.out.println("java 是最好的Web语言"); }; }class PhpLanguage implements ProgrammingLanguage{ public void say(){ System.out.println("php 是最好的Web语言"); }; }public class Demo{ public void whatyousay(ProgrammingLanguage language){ language.say(); } public static void main(String[] args){ ProgrammingLanguage lan = new JavaLanguage(); say(lan); } }

抽象类和接口 抽象类的三个特性(abstract)
  • 抽象类不允许被实例化只能被继承,也就是说你不能new 出来一个抽象类的对象。
  • 抽象类里面有两种属性和方法
    • 抽象属性和方法:只声明,不实现
    • 非抽象属性和方法:声明 + 实现
  • 子类实现抽象方法:子类必须实现父类中的所有抽象方法。
接口的三个属性(interface)
  • 接口不能包含属性
  • 接口只能声明方法,不能包含实现代码(java 8后default可以)
  • 类实现接口类的时候,必须实现接口中声明的所有方法
抽象类和接口的区别
  • 【面向对象与设计原则】抽象类是对成员变量和方法的抽象,是一种is-a关系,是为了解决代码复用问题
    is-a:代表类与类之间的继承关系 比如:猫和狗都是动物,都继承动物的特性。 所谓动物的特性就相当于抽象类中的抽象属性和方法、猫和狗都具有动物的特性,即实现动物的抽象属性

  • 接口仅仅是解决对方法的抽象(抽象类 - 类的抽象),是一种has-a关系,是为了解决解耦问题,提高代码的可扩展性
    has-a:代表对象和它的属性之间的从属关系 同一个类的对象,通过它的属性的不同值来区别 比如:小明和小红 都是同一个类(Person) 但是他们有不同的属性值 Person xiaoming = new XiaoMing("小明")是一个整体和部分的关系

两者再区别
  • 抽象类 - 强调它是什么
    B 继承 抽象类A 并实现了 A 中的抽象属性和方法,那么它就属于 A
    比如 鸟(有翅膀、尖嘴、会下蛋) -继承- B(会说人话):B继承鸟类这个抽象类那么他就得实现鸟类得相关特性,所以它就是一只鹦鹉
  • 接口 - 强调它有什么功能
    Man 和 Woman实现 接口Person 重写 Person 中的相关方法来,但是赋予了不同内容,进而有了不同的能力
总结
  • 抽象类是为了解决代码复用问题
    比如你要写所有的很多鸟相关的类、你把每个鸟都有的属性写在抽象类中、然后再在子类当中写入不同的鸟所特有的属性,这样就不用再在子类中添加这些属性。使得代码高度复用
  • 接口是为了解决抽象问题
    比如说你要写王者荣耀里所有的的英雄的技能,假设这些英雄都只有3个技能,那么我们就在接口中定义(技能一、技能二、技能三)即可、然后每个英雄的技能在子类中具体实现即可。
基于接口而非实现编程 接口意识、封装意识、抽象意识
好的抽象、封装能够提高代码的灵活性,能够更好地应对需求地变化。
如下我们实现一个RabbitMq收发消息的场景,首先,这显然是一种“看似面向对象,实则面向过程”的实现,没有对代码进行有效地抽象,整个发布消息地过程实际上就是,一种代码形式的流水账,另外,一旦需求发生变化后,假如,后期将RabbitMq替换成其它的消息队列,不光是RabbitMqPub类将被废弃,使用到RabbitMqPub的地方也将面临大量的代码修改。
public class RabbitMqPub{ //用于创建MQ的物理连接 private static ConnectionFactory factory = new ConnectionFactory(); static { //设置factory的参数 如地址、用户名、密码等 } private Connection conn; private Channel ch; //获取连接 public Connection getConnection(){ if(connnect != null)return conn; else return factory.newConnection(); } //获取通道 public Channel getChannel(Connection conn){ if(ch != null)return ch; else return conn.newChannel(); } //发送消息 public void publish(Channel ch,String topic,String routeKey,String msg){ ...... } //还有订阅、连接关闭等方法 //...... }public class PublishJob{ RabbitMqPub pub = new RabbitMqPub(); public void publishMsg(){ Connnect conn = pub.getConnection(); Channel ch = pub.getChannel(conn); pub.publish(ch,......); } }

下面是对上有代码的抽象封装并且使用Spring的工厂和反射的思想对MqPub进行配置,在实现多个消息队列的publish后,我们只需要在代码中对相应接口进行调用,而并不需要关心使用的是哪个消息队列,要切换消息队列也仅需在配置文件中进行更改。
public interface MqPub{ //...... public void publish(String topic,String routeKey,String msg); }public class RabbitMqPub implements MqPub{ //用于创建MQ的物理连接 private static ConnectionFactory factory = new ConnectionFactory(); static { //设置factory的参数 如地址、用户名、密码等 } private Connection conn; private Channel ch; //获取连接 private Connection getConnection(){ if(connnect != null)return conn; else return factory.newConnection(); } //获取通道 private Channel getChannel(){ if(ch != null)return ch; else return getConnection().newChannel(); } //发送消息 public void publish(String topic,String routeKey,String msg){ ..... } //还有订阅、连接关闭等方法 //...... }public class PublishJob{ MqPub pub = MqFactory.getMqPub(); public void publishMsg(){ pub.publish(......); } }public Class MqFactory{ private final String mqClassPath; static{ String type = getProperties("XXXX"); if("rabbit".equals(type)){ mqClassPath = getProperties("XXXX"); } } public static MqPub getMqPub(){ Class clz = Class.forName(mqClassPath) return clz.newInstance(); } }

SOLID原则
  • 单一职责原则(Single Responsibility Principle):一个类或一个模块只负责完成一个职责
  • 开闭原则(Open Close Principle):对拓展开放,对修改关闭,前者是为了应对新的需求,后者是为了保证系统的稳定性
  • 里氏替换原则(Liskov Substitution Principle):子类对象能够替代父类对象出现的任何对象,并保证原来程序的逻辑行为不变及正确性不被改变。
  • 接口隔离原则(Interface Segregation Principle):一个类要给多个客户端使用,那么可以为每个客户端创建一个接口,然后这个类实现所有接口,而不要只实现一个接口,其中包含所有客户端使用的方法。
  • 依赖倒置原则(Dependence Inversion Principle):模块间的依赖通过抽象发生,实现类之间不发生直接的依赖,其依赖是通过抽象类或者接口产生。

    推荐阅读