当前位置:   article > 正文

面向对象及其五大基本编程原则[简洁易懂]_简述面对对象程序的基本设计规则

简述面对对象程序的基本设计规则

版权声明:本文为博主「SJMP1974」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
编辑:SJMP1974
原文出处链接:https://editor.csdn.net/md?not_checkout=1&articleId=130183336
参考链接:https://hollischuang.gitee.io/tobetopjavaer/#/menu

面向对象与面向过程

什么是面向过程?

“面向过程”(Procedure Oriented) 是一种以过程为中心的编程思想。最典型的面向过程编程语言是 C 语言。

简而言之,“面向过程”需要将问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。

例如,最典型的用法是实现一个简单的算法,比如冒泡排序

缺点是:代码复用性较差,不易维护。

什么是面向对象?

面向对象(Object Oriented)是软件开发方法,一种编程范式。对现实世界抽象,将属性(数据)、行为(操作数据的函数或方法)封装成类。

例如:想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类。

面向对象的编程语言有Java、C#、C++、Python、Ruby、PHP等。

面向对象有三大基本特征和五大基本原则。

面向对象的三大基本特征——封装、继承、多态

如何将现实世界的事物抽象成代码呢?
运用面向对象的三大基本特征,即封装、继承和多态。

什么是封装?

封装:将客观事物抽象成类(包含属性和方法),并将类的属性和方法仅让可信的类或对象操作,对不可信的进行信息隐藏。
注:【属性】可理解为数据;【方法】可理解为操作数据的函数

例如:
对客观世界的矩形进行封装。

/**
* 矩形
*/
class Rectangle {

    /**
      * 方法:设置矩形的长度和宽度
      */
    public Rectangle(int length, int width) {
        this.length = length;
        this.width = width;
    }

    /**
      * 数据: 长度
      */
    private int length;

    /**
      * 数据: 宽度
      */
    private int width;

    /**
      * 方法:获得矩形面积
      *
      * @return
      */
    public int area() {
        return this.length * this.width;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

什么是继承?

继承:子类不用复写父类的代码,通过继承的方式,可以获得父类的所有功能,即子类继承父类。

例如:
客观世界正方形是矩形的一个特例(正方形的长和宽相等),即长方形拥有的属性和方法,正方形可以通过继承的方式获得。

/**
 * 正方形,继承自矩形
 */
class Square extends Rectangle {

    /**
     * 设置正方形边长
     *
     * @param length
     */
    public Square(int length) {
        super(length, length);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

什么是多态?

多态:一个类的实例的相同方法在不同的情形下有不同的表现形式。

例如,动物是一个抽象类,猫咪和狗是其子类,都继承 eat() 这个方法,变量 Animal animal 指向猫的实现,调用 animal.eat(),就是猫咪吃鱼,如果变量 Animal animal 指向狗的实现,调用 animal.eat(),就是狗吃骨头。

class Animal{
    public abstract void eat(){
    }
}

class Cat extends Animal{
    @Override
    public void eat(){
        System.out.println("猫咪要吃鱼");
    }
}

class Dog extends Animal{
    @Override
    public void eat(){
        System.out.println("狗要吃骨头");
    }
}

public class Demo {
    public static void main(String[] args) {
        Animal animal = new Cat();
        animal.eat();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
猫咪要吃鱼
  • 1

参考:https://blog.csdn.net/qq_44823756/article/details/120535531

总结:实现多态的三个步骤

  • 继承(猫和狗继承动物)
  • 重写方法(猫和狗重写eat方法)
  • 父类引用指向子类对象(Animal animal = new Cat();)

多态有什么好处?

  • 可维护性:在实际开发中, Animal animal = new Cat();这块代码,new Cat() 这块代码可能是一个方法来创建动物对象,因此如果要更改动物的实现方式,只需要更改创建动物对象的方法即可,无需修改主要代码。此例仅帮助大家理解。
  • 可扩展性: 增加新的子类不影响已存在类。

面向对象的三大基本特征——SOLID

  • 单一职责原则
  • 对扩展开放,对修改关闭
  • 里氏替换原则
  • 接口隔离原则
  • 依赖倒置原则

什么是单一职责原则(Single-Responsibility Principle)?

其核心思想为:一个类,最好只做一件事,只有一个引起它的变化。通常地,就是指只有一种单一功能,不要为类实现过多的功能点。

专注地,是一个人优良的品质;同样地,单一也是一个类的优良设计。

注:不仅类的职责要单一,而且方法的职责也要单一。

什么是开放封闭原则(Open-Closed principle)?

其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。

举例:
功能是协议在发送时失败或者超时需要重新发送。

ProtocolRequest request;
bool result = Send(request);
if (!result)
{
    ReSend(request);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

但某些协议不重要,不需要重发。

ProtocolRequest request;
bool result = Send(request);
if (!result)
{
    if (request is PingRequest)
    {
        //do nothing
    }
    else
    {
        ReSend(request);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

如果是这样写代码,就违反了开放封闭原则,因为如果之后还有类似 Ping 的协议,还需要继续修改这个方法。
重构:为ProtocolRequest类新增一个属性 NeedResend,在调用处直接以此判断:

class ProtocolRequest
{
    public int Id
    {
        get; set;
    }

    public virtual bool NeedResend
    {
        get;
    }
}
class PingRequest : ProtocolRequest
{
    public override bool NeedResed
    {
        get
        {
            return false;
        }
    }
}

//for test
ProtocolRequest request;
bool result = Send(request);
if (!result)
{
    if (request.NeedResend)
    {
        ReSend(request);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

这样的话,以后新增协议,需要重发就可以重写这个属性,而不需要修改原有代码。
这就符合了开闭原则。

注:此处抛砖引玉
参考:https://www.jianshu.com/p/6e2874717805

什么是里氏替换原则(Liskov-Substitution Principle)?

其核心思想是:子类必须能够替换其基类,子类扩展父类的功能,但是不能改变父类原有的功能。

why?
因为不遵守里氏替换原则,程序有可能出错。例如:

//父类
class Calculator{
    
    //父类非抽象方法
    public int calculate(int data1,int data2){
         return data1+data2;       
    }
}


class SubCalculatot extends Calculator{

    //子类违反里氏替换原则,重写了父类非抽象方法, 计算差值
    @Override
    public int calculate(int data1,int data2){
        return data1-data2;
    }

    // 其他方法
    ...
}

// 原有业务Demo类
public static void main(String[] args){
    // 原有业务逻辑,引用了父类的非抽象方法 Calculator.calculate() 计算和。
   Calculator calculator = new Calculator();
    calculator.calculate();
}

// 此时,业务需求增加,需要在子类新增一个其他方法,供业务Demo使用
public static void main(String[] args){
    // 为使用新的方法,改为使用子类实现。
   Calculator calculator = new SubCalculatot();
    // 原有逻辑实现发生变化,导致走了子类的逻辑,计算差值
    calculator.calculate();

    // 新增的业务逻辑
    ...
    calculator.otherMethod();
}

// 总结:增加新的业务逻辑,影响了原有的程序,导致错误,因此,要遵循里氏替换原则。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

how?(如何做?)

  • 子类必须完全实现父类的抽象方法,但不能覆盖父类的非抽象方法
  • 子类可以是实现自己特有的方法
  • 当子类的方法实现父类的抽象方法时,方法的后置条件要比父类更严格,例如,父类方法的返回值是 ArrayList,子类方法的返回值不能是更宽泛的条件 List.
  • 子类的实例可以替代任何父类的实例,反之不成立。

参考:https://www.bilibili.com/video/BV1fG411P75c/?spm_id_from=333.337.search-card.all.click&vd_source=09e3f81b9f7ed8ee4306bb2e0ecd5538

什么是接口隔离原则(Interface-Segregation Principle)?

其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。避免类实现接口时,类实现了根本用不上的接口,不强迫依赖不用的方法。

image.png

参考:https://www.bilibili.com/video/BV1344y1S73M/?spm_id_from=333.337.search-card.all.click&vd_source=09e3f81b9f7ed8ee4306bb2e0ecd5538

什么是依赖倒置(Dependecy-Inversion Principle)?

其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。

例如:
在最开始的时候,PizzaStore 通过new的方式创建对象,PizzaStore 是高层组件,CheesePizza 和 ClamPizza 等具体对象是低层组件,如下图所示。
高层组件依赖低层组件.png

package headfirst.designpatterns.factorymethod.background;

import headfirst.designpatterns.factorymethod.Pizzas.CheesePizza;
import headfirst.designpatterns.factorymethod.Pizzas.ClamPizza;
import headfirst.designpatterns.factorymethod.Pizzas.PepperoniPizza;
import headfirst.designpatterns.factorymethod.Pizzas.Pizza;

public class PizzaStore {

    public PizzaStore() {
    }

    public Pizza orderPizza(String type){
        Pizza pizza;
        // ---此部分代码属于变化的部分,可能根据需求变化,需要修改这段代码,不合理---
        if (type.equals("cheese")){
            pizza = new CheesePizza();
        }else if (type.equals("clam")){
            pizza = new ClamPizza();
        }else if (type.equals("pepperoni")){
            pizza = new PepperoniPizza();
        }else{
            pizza = null;
        }
        // --------------------------------------------------------------
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

重构后:
高层组件和低层组件都依赖抽象Pizza.png
PizzaStore 有一个抽象方法 createPizza,子类 ChicagoPizzaStore 和 NYPizzaStore 实现了createPizza,业务在使用时,由业务决定使用哪个子类创建Pizza,PizzaStore 的 orderPizza 方法根据返回的具体Pizza(ChicagoStyleCheesePizza等等),开始制作Pizza,不同子类生产 Pizza 的制作流程由子类自己决定。

public abstract class PizzaStore {
 
    // 工厂方法,是抽象方法
	abstract Pizza createPizza(String item);
   
	public Pizza orderPizza(String type) {
        // 工厂方法生产对象,由于抽象类无法实例化,故生产对象的任务由子类(ChicagoPizzaStore 或 NYPizzaStore 完成,也就是具体的对象生产者)
		Pizza pizza = createPizza(type);
		System.out.println("--- Making a " + pizza.getName() + " ---");
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
        // 调用 other method
		return pizza;
	}
	//******** other method, 可由各工厂实现自己特色 **********
}


public class ChicagoPizzaStore extends PizzaStore {

	Pizza createPizza(String item) {
        	if (item.equals("cheese")) {
            		return new ChicagoStyleCheesePizza();
        	}else return null;
	}
}


public class NYPizzaStore extends PizzaStore {

	Pizza createPizza(String item) {
		if (item.equals("cheese")) {
			return new NYStyleCheesePizza();
		}else return null;
	}
}

public abstract class Pizza {
	String name;
	String dough;
	String sauce;
	ArrayList<String> toppings = new ArrayList<String>();
 
	void prepare() {
		System.out.println("Prepare " + name);
		System.out.println("Tossing dough...");
		System.out.println("Adding sauce...");
		System.out.println("Adding toppings: ");
		for (String topping : toppings) {
			System.out.println("   " + topping);
		}
	}
  
	void bake() {
		System.out.println("Bake for 25 minutes at 350");
	}
 
	void cut() {
		System.out.println("Cut the pizza into diagonal slices");
	}
  
	void box() {
		System.out.println("Place pizza in official PizzaStore box");
	}
 
	public String getName() {
		return name;
	}

	public String toString() {
		StringBuffer display = new StringBuffer();
		display.append("---- " + name + " ----\n");
		display.append(dough + "\n");
		display.append(sauce + "\n");
		for (String topping : toppings) {
			display.append(topping + "\n");
		}
		return display.toString();
	}
}


public class ChicagoStyleCheesePizza extends Pizza {

	public ChicagoStyleCheesePizza() { 
		name = "Chicago Style Deep Dish Cheese Pizza";
		dough = "Extra Thick Crust Dough";
		sauce = "Plum Tomato Sauce";
 
		toppings.add("Shredded Mozzarella Cheese");
	}
 
	void cut() {
		System.out.println("Cutting the pizza into square slices");
	}
}



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101

原来 PizzaStore 依赖 Pizza 的具体实现,不符合依赖倒置原则。经过重构后,PizzaStore 仅依赖 Pizza 抽象类,Pizza 的具体实现类也仅依赖抽象类 Pizza。在这个过程实现了依赖倒置,实现了高层组件与低层组件的分离。

注:原来高层组件依赖低层组件,现在高层组件仅依赖高层组件,低层组件也依赖高层组件,实现了依赖倒置。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/274397
推荐阅读
相关标签
  

闽ICP备14008679号