面向对象
intro
抽象(abstract class)类、和接口(interface),犹记得只在写C++和写java的时候用过,查漏补缺一下.
可能因为经常写接口提供的是数组,导致我形成了一种"面向数组/面向过程"编程的思想,而“忘记”了什么是“面向对象”.
而且最近在读代码时,也看到了久违的“编程思想”,正巧总结一下.
BrainStorm: interface和abstract有什么区别和联系
区别:
1、抽象类需要继承,用extends.接口需要实现,用implements.所以定义的方法必须实现/重写.
2、一个类可以实现多个接口,但只能继承一个抽象类
3、接口中每个方法都只有声明而没有实现,其中的每个方法实现类必须要实现;而抽象类中只需要实现抽象方法,其它方法可以选择性的实现;
4、接口中只能声明public的方法,不能声明private和protected的方法,不能对方法进行实现,也不能声明实例变量;但是抽象类中可以.抽象类中可以使用构造函数(public function __contract)
联系:
1、抽象和接口定义的方法(抽象方法)必须在广义的子类(非抽象子类)中实现/重写
个人理解:
接口(interface)接口故名思议,对外界提供入口的地方,所以也可以说是提供了规范.我觉得“像”是C++中的.h文件或者说是一个模板,接口中声明的方法,在子类中都必须实现.
抽象(abstract)更像是提出了公因式的的方程,将公因式组合起来的方法.如果想到更好的例子我会替换.
从设计模式上来说,抽象(abstract)类中可以实现方法,继承就避免了写很多重复的代码,所以定义abstract类更像是"is-a"的模式.而interface不需要实现具体的接口,具体在子类中实现方法,实现的方式可能有所不同,所以更像是"like-a"的模式.
面向对象的三个特征:
1. 封装:
类封装了数据以及操作这些数据的代码的逻辑实体.
对象内部,某些代码或某些数据可以是私有的,不能被外界访问.通过这种方式,对象对内部数据提供了不同级别的
保护.
2. 继承:
继承是指可以让某个类型的对象获得另一个类型的对象的属性的方法.父类(超类/基类)、子类(派生类)
解释一下:
class A extends B
B是父类/B是A的基类
A是子类/A是B的派生类
3. 多态
多态指一个类实例的相同方法在不同情形有不同表现形式,多态机制使具有不同内部结构的对象可共享相同的外部
接口.实现方式(覆盖/重写 overwrite[子类重写父类方法]/ 重载overload[不同的参数个数,实现重载])
BrainStorm: php如何实现重载(overload)、覆盖(overwrite)、多态(Polymorphism)
php重载(overload)
<?php
/**
* Class Parents
*/
class Parents
{
public function eat($food)
{
echo '啊,中午吃了'.$food."\n";
}
}
/**
* Class Children
*/
class Children Extends Parents
{
}
$oEat = new Children();
$oEat->eat('苹果');
php重写(overwrite)
<?php
class Parents
{
public function eat($food)
{
echo '啊,中午吃了'.$food."\n";
}
}
/**
* Class Children
*/
class Children Extends Parents
{
public function eat($food)
{
echo '啊,晚上吃了'.$food."\n";
}
}
$oEat = new Children();
$oEat->eat('苹果');
简单说一下多态的特征:
1、方法名相同.
2、参数个数不同
3、参数类型不同
先看Java实现重载:
public class Calc
{
public static void main(String[] argc)
{
Calc w = new Calc();
System.out.println(w.Multi(2,3));
System.out.println(w.Multi(2,3,4));
System.out.println(w.Multi(2.0f,3.0f));
}
public int Multi(int x, int y)
{
return x*y;
}
public int Multi(int x, int y ,int z)
{
return x*y*z;
}
public float Multi(float x, float y)
{
return x*y;
}
}
可以看出传入不同的参数参数和参数类型,调用不同的方法.
这么一看,php不允许使用相同的方法名,同时也是弱类型语言,在php7以前好像都不能使用形参.那也不是说明php不能实现重载,通过使用“魔术方法”(真魔术,我还真看不出哪里魔术了).
<?php
class OverLoads
{
public function __construct()
{
$args = func_get_args();
$num = func_num_args();
switch ($num) {
case 1:
self::eatOne($args[0]);
break;
case 2:
self::eatTwo($args[0], $args[1]);
break;
default:
break;
}
}
public function eatOne($food1)
{
echo '我饿了,所以偷吃了' . $food1."\n";
}
public function eatTwo($food1, $food2)
{
echo '我饿了,所以偷吃了' . $food1 . '然后,所以又偷吃了' . $food2."\n";
}
}
$oEat = new OverLoads('橙子');
$oEat = new OverLoads('香蕉','苹果');
你可能会说,php这并不是重载,都没有调用同一个方法,只是在实例化一个类~
<?php
/**
* @method void Eat($food)
*/
class OverLoad
{
public function __call($name, $arguments)
{
if ('Eat' === $name) {
switch (count($arguments)) {
case 1:
self::eatOne($arguments[0]);
break;
case 2:
self::eatTwo($arguments[0], $arguments[1]);
break;
default:
break;
}
}
}
public function eatOne($food1)
{
echo '我饿了,所以偷吃了' . $food1 . "\n";
}
public function eatTwo($food1, $food2)
{
echo '我饿了,所以偷吃了' . $food1 . '然后,所以又偷吃了' . $food2 . "\n";
}
}
$oEat = new OverLoad();
$oEat->Eat('橙子');
$oEat->Eat('香蕉', '苹果');
BrainStorm: 什么是php“魔术常量/方法”
魔术常量
PHP 向它运行的任何脚本提供了大量的预定义常量。不过很多常量都是由不同的扩展库定义的,只有在加载了这些扩展库时才会出现,或者动态加载后,或者在编译时已经包括进去了。
详情请看链接
__LINE__ 文件中的当前行号。
__FILE__ 文件的完整路径和文件名。如果用在被包含文件中,则返回被包含的文件名。自 PHP 4.0.2 起,__FILE__ 总是包含一个绝对路径(如果是符号连接,则是解析后的绝对路径),而在此之前的版本有时会包含一个相对路径。
__DIR__ 文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录。它等价于 dirname(__FILE__)。除非是根目录,否则目录中名不包括末尾的斜杠。(PHP 5.3.0中新增) =
__FUNCTION__ 函数名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该函数被定义时的名字(区分大小写)。在 PHP 4 中该值总是小写字母的。
__CLASS__ 类的名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该类被定义时的名字(区分大小写)。在 PHP 4 中该值总是小写字母的。类名包括其被声明的作用区域(例如 Foo\Bar)。注意自 PHP 5.4 起 __CLASS__ 对 trait 也起作用。当用在 trait 方法中时,__CLASS__ 是调用 trait 方法的类的名字。
__TRAIT__ Trait 的名字(PHP 5.4.0 新加)。自 PHP 5.4 起此常量返回 trait 被定义时的名字(区分大小写)。Trait 名包括其被声明的作用区域(例如 Foo\Bar)。
__METHOD__ 类的方法名(PHP 5.0.0 新加)。返回该方法被定义时的名字(区分大小写)。
__NAMESPACE__ 当前命名空间的名称(区分大小写)。此常量是在编译时定义的(PHP 5.3.0 新增)。
魔术方法
PHP 将所有以 (两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 为前缀。
我觉得搞出这些魔法方法是"fixbug",支持OOP的设计模式而已.因为弱类型的关系,实现的多态我觉得是“伪多态”.具体魔术方法的详情请看php官方手册魔术方法
__construct() //构造函数 **构造函数设成私有或者protected,就不能实例化该对象了**
__destruct() //析构函数
__call() //Magic Call
__callStatic() //Magic Static Call
__get()
__set()
__isset()
__unset()
__sleep() //序列化时调用
__wakeup() //反序列化时调用
__toString()
__invoke() //调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
__set_state()
__clone()
__debugInfo()
面向对象
如果我是面试官,你对面向对象的理解?和面向过程有什么区别?
如果照本宣科的说出面向对象的三个特征,五大原则,也许不会另面试官满意,其实主要是个人的理解!!!
引用知乎的回答:
如何大象装进冰箱?
面向过程:
为了把大象装进冰箱,需要3个过程。
1) 把冰箱门打开(得到打开门的冰箱)
2) 把大象装进去(打开门后,得到里面装着大象的冰箱)
3) 把冰箱门关上(打开门、装好大象后,获得关好门的冰箱)每个过程有一个阶段性的目标,依次完成这些过程,就能把大象装进冰箱。
1:冰箱开门(冰箱)冰箱装进(冰箱, 大象)冰箱关门(冰箱)==换个写法(冰箱开门 冰箱)(冰箱装进 冰箱 大象)(冰箱关门 冰箱)
2:冰箱关门(冰箱装进(冰箱开门(冰箱), 大象))==换个写法(冰箱关门 (冰箱装进 (冰箱开门 冰箱) 大象))
面向对象:
为了把大象装进冰箱,需要做三个动作(或者叫行为)。每个动作有一个执行者,它就是对象。
1) 冰箱,你给我把门打开
2) 冰箱,你给我把大象装进去(或者说,大象,你给我钻到冰箱里去)
3) 冰箱,你给我把门关上依次做这些动作,就能把大象装进冰箱。
1:冰箱.开门()冰箱.装进(大象)冰箱.关门()
2:冰箱.开门().装进(大象).关门()
再举一个简单的例子:人吃饭.
面向过程强调“吃”,"人"是一个参数,考虑具体怎么吃,什么时间该吃
面向对象强调“人”,“人"有吃的功能.具体什么时间该吃,怎么吃,吃什么我不关心.
个人理解
初步理解:面向过程更关于于事物本身的“外在特性/基础功能”,将事物的“外在特性/基础功能”提取出来.例如:冰箱本身就是可以打开/关闭/冷藏功能. 具体要放什么东西进去,怎么放,放在那一层,冷藏还是常温我大冰箱根本就不关心,是由具体使用的人来实现.而不能片面的认为只要是使用了对象就是面向对象.而面向过程是制定好了一些具体的步骤,首先先看是什么食物,是否需要冷藏,然后打开冰箱关闭.
如果在设计中一直坚持这么设计好像也没有什么毛病,只是如果你不会“抽象”/不分析问题,也会形成一种"面向对象/面向object"的毛病.强行为了使用“面向对象”而“面向对象”.
面向对象并不是为了复用和扩展,或者说不是为了复用和扩展才使用了面向对象,面向对象只是分析问题的一个方法.通过不同程度的“抽象“去简单化问题,再降低抽象的维度去细化解决问题.
未完待续~