深入理解面向对象,面向对象3个特性7个原则6种关系

  1. 依照PHP的语法为主,不同的编程语言,有不同的方式,但大体上都是一样的。
  2. 面向对象的思想在于理解和使用。
  3. 文章所说的接口,都是编程语言语法的接口(interface),而不是前端调用的接口(api)。

为什么会有这么多的概念?知其所以然。

软件,是为了解决人类发展的问题,方便人类的工具,如果一个计算机,或者一个软件只会算数,那么遇见汇率换算怎么办?是不是可以说计算机本身如果没有太多的固有功能,就无法完成某些事。而现在的软件,可以帮助人们购物,相亲,娱乐。这么复杂的功能来说,如果软件思想没有发展出更高级的思想,它是无法为人类服务的,所以为了可维护性更强,具有更强的功能,必须在计算机软件编程本身上提出一些更高级的概念,用于实现比算加减乘除更复杂的功能,多样化的规则成了一套实用高效的规范,那么就是下文看到的各种计算机名词。

面向对象

面向

“面向”此处可以理解为按照什么思路去编程。

对象

对象是一种依照事物为中心的编程思想,万物皆对象,由实体引发事件,对象是真实存在的,对象也可以理解为是把数据结构和处理它们的方法,组成对象。达到了软件工程的三个目标,重用性灵活性和扩展性。
重用性: 一个类里面的方法可以多次使用。
灵活性: 可以表现为多态,可重复调动等特点,自由度很高,条条大路通罗马。
扩展性: 多态,继承都有这个特性,可便于多样化扩展,进行抽离,降低耦合。

可以理解为对象的抽象化概念,分离出相同的特点,并加以归类。
把相同行为的对象归纳为类,一个人是一个对象,但是多个人可以归纳为一个人类,人类指的不是某一个,而是一个虚拟的实体概念。为什么有人这个类?因为人有双手,会用火,有文明,学习能力强等因素,如果想要从人类上说某个人,那就是举一个实例,也就是实例化。

成员属性

属性可以理解数据,数据是信息和表现形式的载体。依照人为例,人有鼻子有眼,这个就是人的属性。可以用 来形容。

成员方法

方法可以理解为函数。方法是控制数据的管家。依照人为例,人会说话,会吃饭,这就人的方法。可以用 来形容。

接口

可以理解为一个类中需要指定它需要做什么,但是不需要去做,起到一个规范示例的作用,就是一个标准,需要其它的类去实现它。例如定义了usb接口的尺寸,大小,数据线连接方法,可以类比成一个接口规范,全世界的usb接口都通用,无论是U盘,充电器线,鼠标,键盘。这些实例都可以根据这个约束规范,去制造东西。接口的使用需要用 实现 两个字形容。

类的三个特性

封装: 通过封装隐藏类的内部方法或者数据,只允许访问可以访问的资源,保证安全性。拟人化来说,就是姓名是公开的,银行密码的信息是个人的,PHP可用public protected private去修饰。

继承: 继承使类得到泛化,儿子继承爸爸,可以获得父级或者父级往上的非私有属性,或者方法,使用extends关键字。

多态: 可实现基于对象类型的动态分派,不同的人做同一件事,得到不同的结果。相亲这件事:女生遇到流氓会说滚,遇到帅哥会说么么哒。

面向对象场景下编程7个原则,宗旨:面向接口编程,针对目标。精简代码,降低耦合。灵活分离,减少影响。抽离相同的代码,便于复用维护。

单一原则: 一个类就做一件事,职责被完整的封装在一个类中。不会引起混乱,提高重用性,低耦合的设计可以降低软件开发后期的维护。

开闭原则: 对修改关闭,对扩展开放。因为需求变更,运行环境升级等原因需要改代码,如果没有出现bug,那么不推荐改原来的代码,可以在原基础上进行扩充。既增加了扩展性,又保证原来的逻辑不出问题。所谓的“祖传代码,勿动”,也就是这么一回事。

里氏代换原则: 通俗讲:所有能用到老爸的地方,儿子也能使用。软件中能够使用的基类对象,那么就要做到任何地方能使用子类对象而不受影响。也就是子类能够替换程序中父类出现的任何地方,并保证逻辑不变和正确,这里面暗含着不推荐重写父类的意思。正是因为有了这个标准,才能防止父级一旦修改,就会殃及子级发生故障的情况。这个原则实现了开闭原则的形式,子类就相当于扩展。实质上这个原则是要告诉我们,继承需要注意的问题和遵循的原则。但继承是增加了父子类的耦合关系,为了解决依赖,可以适当通过聚合,组合,依赖等来解决。

依赖倒转原则: 就是面向接口编程。把共用的可复用的方法放置到抽象类,抽象思维编程,或者接口当中,依照抽象耦合的方式是原则的关键,目的是降低耦合。高层模块(举个例子:高层模块就是框架底层的代码,依照PHP为例,框架底层的代码,好多都是抽象类或者接口)不依赖底层模块,二者依赖于抽象。抽象不应该依赖细节,细节应该依赖抽象,也就是做到细节和非细节的分离,相对于细节的多变性,抽象的东西要稳定的多。抽象为基础搭建的架构比细节为基础的架构要稳定的多。举个例子,功能的大体实现可以使用抽象类,但是细节,可以使用具体的类去实现。这里所谓的抽象,是用于定制规范和整体架构。
举个栗子:

//这段代码适用于发送邮件,但是要增加发送微信的功能,是不是就显得麻烦了?这里不要抬杠说把“Email $email”去掉,如果项目中大量的业务逻辑已经这样写了,把“Email $email”去掉,可能要出问题的。
<?php
class Email {
   public function send() {
        return '发送电子邮件';
   }
}

class WeChat {
	public function send() {
        return '发送微信消息';
   }
}

class Person {
	public function receive(Email $email) {
		return $email->send();
	}
}
$person = new Person();
echo $person->receive(new Email());

再看看 优化的结果,所谓的抽象就是发送信息,细节就是发送信息的方式和内容,抽象和细节做了分离,降低耦合。

<?php

//接口定义大致的方法,大方向
interface SendMsg {
	public function send();
}

//具体的类去实现细节
class Email implements SendMsg {
   public function send() {
        return '发送电子邮件';
   }
}

//具体的类去实现细节
class WeChat implements SendMsg {
	public function send() {
        return '发送微信消息';
   }
}

//此处降低耦合
class Person {
	public function receive(SendMsg $send_msg) {
		return $send_msg->send();
	}
}


$person = new Person();
//灵活调用
echo $person->receive(new Email());
echo $person->receive(new WeChat());

接口隔离原则: 还是为了降低耦合,减少接口类中臃肿的代码段,一个类对另一个类的依赖应该建立在最小的接口上,通俗的讲就是需要什么就提供什么,不需要的就不要提供。比如接口类Test一共有func1,func2,func3,func4,func5 5个方法,A类依赖Test接口 func1,func2,func3三个方法,B类依赖Test接口 func1,func2,func4三个方法。这样的接口func5就没有用上,应该做接口拆分,拆分成A类和B类依赖的接口仅仅够用程度的接口即可,并且增加安全性,这里的拆,就是具有隔离不需要的代码的作用。

<?php
//接口定义了5个方法
interface TestInterface {
   public function func1();
   public function func2();
   public function func3();
   public function func4();
   public function func5();
}

//A类依赖了接口中123三个方法,但func4 和 func5用不上
class A implements TestInterface {
   public function func1() { return '实现了TestInterface接口的func1方法';}
   public function func2() { return '实现了TestInterface接口的func2方法';}
   public function func3() { return '实现了TestInterface接口的func3方法';}
   public function func4() { return '这两个方法用不上,但是由于PHP语法问题,需要去实现';}
   public function func5() { return '这两个方法用不上,但是由于PHP语法问题,需要去实现';}
}


//A类依赖了接口中124三个方法,但func3 和 func5用不上
class B implements TestInterface {
   public function func1() { return '实现了TestInterface接口的func1方法';}
   public function func2() { return '实现了TestInterface接口的func2方法';}
   public function func3() { return '这两个方法用不上,但是由于PHP语法问题,需要去实现';}
   public function func4() { return '实现了TestInterface接口的func4方法';}
   public function func5() { return '这两个方法用不上,但是由于PHP语法问题,需要去实现';}
}

再看不臃肿的代码:

<?php
interface TestInterface_A {
	public function func1();
	public function func2();
	public function func3();
}

interface TestInterface_B {
    public function func1();
    public function func2();
    public function func4();
}

//A类实现了接口中123三个方法,无需依赖额外的方法
class A implements TestInterface_A {
	public function func1() { return '实现了TestInterface接口的func1方法';}
	public function func2() { return '实现了TestInterface接口的func2方法';}
	public function func3() { return '实现了TestInterface接口的func3方法';}
}


//A类实现了接口中124三个方法,无需依赖额外的方法
class B implements TestInterface_B {
    public function func1() { return '实现了TestInterface接口的func1方法';}
    public function func2() { return '实现了TestInterface接口的func2方法';}
    public function func4() { return '实现了TestInterface接口的func4方法';}
}

合成复用原则: 多用组合(has-a),少用继承(is-a),可以降低类与类之间的耦合程度。遇见额外增加的功能,需要扩展,通过关联,而不是继承。因为使用继承,后期改父级代码可能会株连子级,引起错误。
继承复用又称之为白箱复用,组合聚合使用称之为黑箱复用。

迪米特法则/最少知道原则 一个软件实体尽可能少的与其它实体发生作用,限制了编程中通信的宽度和深度,不管依赖的类有多么的复杂,都尽量把逻辑封装到类内部,除了对外提供public的方法,不对外泄露任何无关信息。只和朋友通信,朋友通常是当前类、当前对象、成员属性,成员方法参数,成员方法返回值这些。其余的都是陌生人,不可直接调用。
法则又可以分为狭义法则和广义法则。狭义的说法是:类A和类B发生关联,类B和类C发生关联,那么类A和类C是不能直接访问的,需要通过B。优点是降低耦合,缺点是需要大量的局部化设计,让类A可以访问类C,造成模块间使用效率降低。广义的说法是:尽量创建松耦合的类,控制信息的过载,类之间的耦合度越低,越有利于复用,但是需要更高的抽象分离思想。

并由此出现了23种设计模式,设计模式用于解决经典场景下的经典问题而出来的通用实用规范。但23种有些并不适用于PHP语言,一旦强制使用,就缺失了弱类型语言的优点。

扩展

面向过程

面向过程是依事件为中心,分析出解决问题的步骤,将代码分成若干个过程/函数,一步步实现,其中,函数或过程是最小的模块封装单位,然后调用这些函数,可以称之为方法。所谓的通俗讲封装就是把一堆代码括起来。
面向对象也是基于面向过程的思想,面向对象实现,也必定有相应的过程,所以有很多相同点。

过程与函数的区别

过程: 无返回值。
函数: 有返回值。

类之间的6种关系:继承、依赖、关联、聚合、组合、泛化、实现

继承: 儿子继承爸爸,子类可以使用父类的任何非私有成员方法或成员属性,且可以随意扩展,并且可以通过子类重写放宽对资源的访问修饰,且可以重写父类非私有的的方法或成员属性。很好理解,不多解释。

依赖: 假设有两个类:A和B,类B的某个成员方法的参数有类A,则类B依赖类A (也就是常说的uses-a,这里应该是uses-b)。

<?php
class A {
    public function one() {
        return 'one';
    }
}
class B {
    public function two(A $a) {
        return $a->one();
    }
}
$b_obj = new B();
echo $b_obj->two(new A());

关联: 强依赖关系,当所依赖的类已经成为了类成员的时候,此时会被加载到内存当中的,而不是参数的时候(如果是单纯的依赖,那么依赖所在的方法如果不调用,就不会发生什么),关联有一对多,多对多之分,也有单向双向之分,比如单向一对多。

<?php
class A {
    public function one() {
        return 'one';
    }
}
class B {
    public $obj_a;
    public function two() {
        $this->obj_a = new A();
        return $this->obj_a->one();
    }
}
$b_obj = new B();
print_r($b_obj->two());

聚合: A和B的关系是整体和局部的关系(has-a),整体和局部是可以分开的。DemoDateTime包含了DemoDate和DemoTime,也就是DemoDateTime聚合了DemoDate和DemoTime。

<?php
class DemoDate {
    public function getDate() {
        return date("Y/m/d");
    }
}
class DemoTime {
    public function getTime() {
        return date("H:i:s");
    }
}

class DemoDateTime {
    public function getDateTime() {
        $date_obj = new DemoDate();
        $time_obj = new DemoTime();
        $datetime = $date_obj->getDate() . ' ' . $time_obj->getTime();
        return $datetime;
    }
}
$datetime_obj = new DemoDateTime();
echo $datetime_obj->getDateTime();

组合: 组合关系也是整体和部分的关系,但它是强聚合关系。可以理解为各个部分不能离开整体,离开了就要出问题。

泛化

就是继承。

实现

实现就是一个抽象类被其它类实现。

面向接口编程

通俗讲就是多用用接口,把接口作为定义大骨架,来使用,剩下的细节,交给实现接口的类去使用,也就是将定义与实现分离,需要开发者拥有分离的抽象思想。

什么时候使用抽象类什么时候使用接口?

(以上内容为原创,以下内容全部来源于网络,然后进行整合,感谢各位网友的回复。)
接口应有两类:第一类是对一个体的抽象,它可对应为一个抽象体(abstract class);第二类是对一个体某一方面的抽象,即形成一个抽象面(interface),一个体有可能有多个抽象面。
 
操作数据库就必须会用到 Insert Update Select ,所以Insert Update Select 做成接口
但是,每个功能操作的内容又不一样,所以,做一个抽象类继承接口然后抽象类的派生类去实现抽象类的具体方法。

接口是一组规则的集合,它规定了实现本接口的类或接口必须拥有的一组规则
抽象类和接口的区别在于使用动机。使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态性。

如果这个概念在我们脑子中是确确实实存在的,就用抽象类。
否则的话,如果这个概念仅仅是一方面的特性,比如会飞的,能跑的,这些我们就设置为接口。
两个概念模糊,不知道设置为抽象类还是接口的时候,一般我们设置为接口,原因是我们实现了这个接口还可以继承。

抽象类适合用来定义某个领域的固有属性,也就是本质,接口适合用来定义某个领域的扩展功能。
当需要为一些类提供公共的实现代码时,应优先考虑抽象类。因为抽象类中的非抽象方法可以被子类继承下来,使实现功能的代码更简单。
当注重代码的扩展性跟可维护性时,应当优先采用接口。①接口与实现它的类之间可以不存在任何层次关系,接口可以实现毫不相关类的相同行为,比抽象类的使用更加方便灵活;②接口只关心对象之间的交互的方法,而不关心对象所对应的具体类。接口是程序之间的一个协议,比抽象类的使用更安全、清晰。一般使用接口的情况更多。

当描述一组方法的时候使用接口
当描述一个虚拟的物体的时候使用抽象类

抽象类是跟继承类是“is”的关系,接口和实现类是"like"的关系,从这个角度出发,应该可以看出很多东西。其它的区别只是一些语法,用法规范上的区别,核心思想就是这个is-like区别。

posted @ 2021-04-18 11:59  小松聊PHP进阶  阅读(443)  评论(0编辑  收藏  举报