在刚开始了解Java时,听到最多的词语就是“面相对象”,那时候还是懵懵懂懂,隐约的能体会到面相对象是个什么意思,但是具体也说不出什么云云…随着实践经验的不断积累,现在终于有了一点关于面相对象的眉目。
下面就从三大特性和设计原则方面谈一下面相对象的思想。
目录 (Table of Contents)
一、三大特性
1、封装
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外的接口使其与外部发生联系。用户无需关心对象内部的细节,但可以通过对象对外提供的接口来访问该对象。封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
封装有以下的优点:
- 减少耦合:可以独立地开发、测试、优化、使用、理解和修改
- 减轻维护的负担:可以更容易被理解,并且在调试的时候可以不影响其他模块
- 有效地调节性能:可以通过剖析来确定哪些模块影响了系统的性能
- 提高软件的可重用性
- 降低了构建大型系统的风险:即使整个系统不可用,但是这些独立的模块却有可能是可用的
实例:
1、实体Bean类BlogPreview
对象的实体类:
@Data
@ApiModel(value = "博客预览")
public class BlogPreview {
@Id
@Column(name="id")
@ApiModelProperty(value="id", example = "1")
private Integer id;
@Column(name="title")
@ApiModelProperty(value="title", example = "文章标题")
private String title;
@Column(name="imageurl")
@ApiModelProperty(value="imageurl", example = "文章标题")
private String imageurl;
@Column(name="description")
@ApiModelProperty(value="description", example = "文章标题")
private String description;
@Column(name="top")
@ApiModelProperty(value="top", example = "1")
private Integer top;
@Column(name="create_time")
@ApiModelProperty(value="create_time", example = "文章标题")
private String createTime;
@Column(name="views")
@ApiModelProperty(value="views", example = "1000")
private Integer views;
@Column(name="category")
@ApiModelProperty(value="category", example = "博客")
private String category;
}
这里使用了lombok来替代set,get方法。一个博客的预览图大概有以上这几个属性,把这些属性封装成一个BlogPreview类作为一个整体,可以很方便的对其进行操作,使用该对象时,只需要设置其属性即可。
2、工具类封装DataUtils
/**
* @program: my-practices
* @description: 时间工具类
* @author:
* @create: 2019-12-16 13:42
**/
public class DateUtils {
private static final String STANDARD_TIME = "yyyy-MM-dd HH:mm:ss";
private static final String STANDARD_TIME_DAY = "yyyy-MM-dd";
/**
* @Description: 获取时间 格式【2019-12-30 10:14:55】
* @Param:
* @Date: 2019/12/16 0016
*/
public static String currentStandardDate(){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateUtils.STANDARD_TIME);
return simpleDateFormat.format(new Date());
}
/**
* @Description: 获取时间 格式【2019-12-30】
* @Param:
* @Date: 2019/12/16 0016
*/
public static String currentStandardDate2(){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateUtils.STANDARD_TIME_DAY);
return simpleDateFormat.format(new Date());
}
}
直接调用封装的方法,无需关系具体细节
将获取时间的方法封装到一个工具类中,使用时只需关注该类的方法,而不必关系其中的细节。当需要修改该类其中代码时,只需修改工具类中的相关代码,而不必修改调用该类的程序片段。
2、继承
继承实现了 IS-A 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得 Animal 非 private 的属性和方法。
继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 向上转型 。
不使用继承时:
public class Cat {
private String name;
private String category;
private String features;
}
public class Dog {
private String name;
private String category;
private String features;
}
public void testInherit(){
Cat buouCat = new Cat();
buouCat.setName("布偶猫");
buouCat.setCategory("猫");
buouCat.setFeatures("除了贵,没有任何缺点,本仙女就是好看...");
Dog erhaDog = new Dog();
erhaDog.setName("哈士奇");
erhaDog.setCategory("狗");
erhaDog.setFeatures("燃烧智商长大的生物,所它是智障都侮辱了智障...");
}
使用继承时:
@Data
public class Animal {
private Integer id;
private String name;
private String category;
private String features;
}
@Data
public class Dog extends Animal{
private String function;
}
@Data
public class Cat extends Animal{
private String bark;
}
public void testInherit(){
Cat buouCat = new Cat();
buouCat.setName("布偶猫");
buouCat.setCategory("猫");
buouCat.setFeatures("除了贵,没有任何缺点,本仙女就是好看...");
buouCat.setBark("喵喵喵~");
Dog erhaDog = new Dog();
erhaDog.setName("哈士奇");
erhaDog.setCategory("狗");
erhaDog.setFeatures("燃烧智商长大的生物,所它是智障都侮辱了智障...");
erhaDog.setFunction("拆家");
}
使用继承后,不但可以使用父类的非private方法,而且可以添加属于自己类的属性和方法。
3、多态
多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
@Data
public class Shape {
//宽
private Double width;
//长
private Double length;
//高
private Double height;
public Shape(Double width, Double length) {
this.width = width;
this.length = length;
}
public Shape() {
}
public Shape(Double width, Double length, Double height) {
this.width = width;
this.length = length;
this.height = height;
}
public double area(){
//计算图形的面积
return 0;
}
}
//矩形
public class Rectangle extends Shape{
Rectangle(double width, double length){
super(width, length);
}
public double area(){
System.out.println("矩形面积为:");
return super.getWidth() * super.getLength();
}
}
//梯形
public class Trapezoid extends Shape{
Trapezoid(double length,double width,double height){
super(length,width,height);
}
public double area(){
System.out.println("梯形面积为:");
return super.getHeight()*(super.getLength()+super.getWidth())*0.5;
}
}
@Test
public void testPolymorphism(){
Shape rectangle = new Rectangle(10.0,10.0);
Shape trapezoid = new Trapezoid(5.0,5.0,10.0);
double area1 = rectangle.area();
System.out.println(area1);
System.out.println("-----------");
double area2 = trapezoid.area();
System.out.println(area2);
}
/**输出
矩形面积为:
100.0
-----------
梯形面积为:
50.0
**/
二、设计原则
1.单一责任原则
修改一个类的原因应该只有一个。
换句话说就是让一个类只负责一件事,当这个类需要做过多事情的时候,就需要分解这个类。
如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱这个类完成其它职责的能力。
2.开放封闭原则
类应该对扩展开放,对修改关闭。
扩展就是添加新功能的意思,因此该原则要求在添加新功能时不需要修改代码。
符合开闭原则最典型的设计模式是装饰者模式,它可以动态地将责任附加到对象上,而不用去修改类的代码。
3.里氏替换原则
子类对象必须能够替换掉所有父类对象。
继承是一种 IS-A 关系,子类需要能够当成父类来使用,并且需要比父类更特殊。
如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。
4.接口分离原则
不应该强迫客户依赖于它们不用的方法。
因此使用多个专门的接口比使用单一的总接口要好。
5.依赖倒置原则
高层模块不应该依赖于低层模块,二者都应该依赖于抽象;
抽象不应该依赖于细节,细节应该依赖于抽象。
高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于低层模块,那么低层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。
依赖于抽象意味着:
- 任何变量都不应该持有一个指向具体类的指针或者引用;
- 任何类都不应该从具体类派生;
- 任何方法都不应该覆写它的任何基类中的已经实现的方法。
参考
1、面向对象思想
2、Java封装
3、Java多态性:Java什么是多态?