栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

你理解的面向对象可能是错的

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

你理解的面向对象可能是错的

OOP 的表象
  1. 封装
  2. 继承
  3. 多态

三大特征嘛,都能倒背如流,谁不知道呢,是吧?
所以 你会发现大多数面向对象的代码长得差不多,总结下来有个亲切的称谓套路三板斧

  • 第一式,封装
class calculate_base {
public:
	virtual void add(int num) = 0;
	virtual void minus(int num) = 0;
	virtual bool check(int num) {
		printf("this is basen");
		return false;
	}
};
  • 第二式,继承
class add_son :public calculate_base
  • 第三式,多态
class add_son :public calculate_base {
private:
	uint64_t x = 666;
public:
	void add(int num) { x = x + num; }

	void minus(int num) { //毛用不起,还非要实现 }

	bool check(uint64_t num) { //为毛调用不到我, num 确实有 uint64_t的需求啊
	  printf("this is sun_addn");
		if (num == x) {
		    return true;
		}
		return false;
	}
};

一套下来,行运流水般丝滑,当你沉醉于多么标准的 OOP 代码时,你是否想过下面问题:

  1. add_son 加法类为什么要实现一个minus减法?
  2. check多态能被调用吗?

此处可能有杠精,你不会改基类啊?!
确实可以改基类,然后你把继承 calculate_base 的所有子类小心翼翼地全撸一遍(可能不全是你写的),可能还会引入 bug,多么酸爽的领悟

到这,你可能心存疑虑,但又说不出缘由,多年的 OOP 信仰,怎么感觉有点不对劲呢?

OOP 的历史与扭曲

对象范式的原始概念根本不包括类和继承,只有

  • 程序是由对象组成的
  • 对象之间互相 “传递消息”,协作完成任务

而教条式的 OOP 已经扭曲了原有 OOP 的含义。多数人认为写面向对象一定要符合上面的套路三板斧形式。

究其缘由还有一个小故事:

c++之父在博士期间深入研究过第一个面向对象的语言Simula,非常欣赏Sinula语言的思想,故c++面向对象思路受Sinula语言影响,采用调用目标对象的方法来“传递消息”,所以需要事先知道这个对象本身有哪些方法。
因此,定义对象本身有哪些方法的 “类” 和 “继承” 的概念,一下超越了对象本身,而对象只不过是类这个模子里 “造出来的” 东西,反而变得不重要。
随着c++的大行其道,继承、封装、多态变成了面向对象世界的核心概念,至此OOP被扭曲为COP --- Class Oriented Programming (面向类程序设计)

这套三板斧COP 概念本身是有缺陷的 。用起来更像是为了聊天而去相亲…说错了,是为了面向对象而去面向对象。

实现起来似乎要这样 :

程序员成为 ⟶ longrightarrow ⟶ 领域专家 ⟶ longrightarrow ⟶ 领域分类学专家 ⟶ longrightarrow ⟶ 构造一个完整的继承树 ⟶ longrightarrow ⟶ new 出对象 ⟶ longrightarrow ⟶ 程序跑起来

理解起来似乎要这样 :

你想要了解清楚眼前的这位妹子,不但要知道她已有的一切(所有成员),还要知道她祖上的一切(继承链),更要知道这位妹子做了什么叛逆行为(虚函数重写),还不得不知道,这些叛逆行为,会对祖传的既有秘方产生什么样的影响(调用虚函数的函数)

1990 年代中期,COP 问题已经十分明显,不过历史的车轮总是在前进

  • 1994 年 Robert C. Martin 在《Object-Oriented C++ Design Using Booch Method》中,建议面向对象设计从对象活动图入手,而不是类图
  • 1995 年 经典作品《Design Patterns》中,建议优先考虑组合而不是继承,也是尽人皆知的事情
  • 2000 年 delphi 之父在创建 .Net Framework 时,不想要继承
  • 2000 年后 工程界明确的提出:“组合比继承重要,而且更灵活”
  • 2007 年 go 语言放弃继承
  • 2015 年 rust 语言放弃继承

强行用套路三板斧的 COP 来组织代码,至少会面临以下问题:

1. 强制继承,侵入绑架,污染后续业务代码
2. 面向基类编程,追求形同,基类变动,子类也需要改动。增加维护难度
3. 代码以树形结构组织,继承越多,层级越深,代码越复杂。增加理解难度
4. 虚函数,弱规则,基于约定。增加出错可能性
5. 纯虚函数,强规则,冗余实现。增加多余无用代码
6. 基于虚函数的多态,带来性能损耗的惩罚

当然套路三板斧的 COP 代码,在某些情况下是必要的

  • 类型擦除,比如 std::function 的实现
  • 某些特定 行为可能需要被改写,比如 QCombobox 实现右侧删除键

OOP 的本质

OOP 的本质是抽象,和任何实现手段无关。

抽象具化
属性数据
行为接口

一定是抽象正向驱动实现,而不是实现反向驱动抽象。
怎么理解呢?假设有一个需求,让集合中的动物们都说一句 hello word,典型的异构类型多态。

一个反向驱动的例子
class animal {
public:
	virtual void say(const std::string& c) = 0;
};

class dog :public animal {
public:
	void say(const std::string& c) {
		printf("dog say:%sn", c.c_str());
	}
};

class cat :public animal {
public:
	void say(const std::string& c) {
		printf("cat say:%sn", c.c_str());
	}
};

class pig :public animal {
public:
	void say(const std::string& c) {
		printf("pig say:%sn", c.c_str());
	}
};

int main() {
	std::vector> animals;
	animals.emplace_back(new dog);
	animals.emplace_back(new cat);
	animals.emplace_back(new pig);

	for (auto&& animal : animals) {
		animal->say("hello word");
	}
	return 0;
}
  • 因为动物们都说一句,所以每个动物类需要有 say 接口
  • 因为集合中的动物们,所以每个动物类需要一个相同的基类,然后才可以加入到一个集合中
  • 因为需要集合中的动物们都说一句,所以基类需要有个 say 虚接口,动物类都实现这个 say 接口

明显的三板斧COP 代码,实现完成,所以抽象完成。但是进一步想:

  • 如果 dog 喜欢说废话,需要再加一个 extra 参数呢?
  • 如果 cat 喜欢重复说话,需要指定个重复次数呢?
  • 如果 pig 喜欢特立独行,只说给定话语的摘要呢?

明显地它们都有 say 能力,但是个体差异很明显,导致 say 行为也有差异。
强制继承,侵入绑架一模一样的 say 行为,明显就不合理。
现实就是如此,对象可能会有相同的行为,但是行为的表现力却是不一样的

所以需要抽象一种普适的 concept 行为,在需要时,辅以特定的处理,以抽象驱动实现

一个正向驱动的例子
class dog {
public:
	void say(const std::string& c, const std::string& extra = "") {
		printf("dog say:%sn", c.c_str());
		if (!extra.empty()) {
			printf("dog extra say:%sn", extra.c_str());
		}
	}
};

class cat {
public:
	void say(const std::string& c, int count = 1) {
		for (int i = 0; i < count; i++) {
			printf("cat say:%sn", c.c_str());
		}
	}
};

class pig {
public:
	void say(uint64_t c) {
		printf("pig say:%llun", c);
	}
};

#define ANIMALS dog,cat,pig
int main() {
	std::vector> animals;
	animals.emplace_back(dog{});
	animals.emplace_back(cat{});
	animals.emplace_back(pig{});

	for (auto&& animal : animals) {
		std::visit([](auto&& animal) {
			using animal_type = typename std::remove_const_t>;
			if constexpr (std::is_same_v) {
				animal.say(std::hash()("hello word"));
			}
			else {
				animal.say("hello word");
			}
		}, animal);
	}
	return 0;
}

根本就不关心所谓的基类,所谓的继承,所谓的抽象行为接口。
对象本就应改如此简约,让对象回归本质。不为实现而抽象,让抽象回归对象

结束语

历史让 c++走上舞台,历史也终将让 COP 重新回到 OOP 的本来面目,谨以此文,尝试唤醒那些深陷COP 的人。。。
其实,很多时候,人需要尝试从另一个方向去否定当下的自己。在和自己的攻防斗争中,才能加深对事物的理解,不被事物的某一面所蒙蔽

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/951929.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号