参考《c++ primer》
参考《松本行弘的程序世界》
- 面向对象有抽象封装,继承,多态三大特性
- 抽象封装通过private,public等控制数据和方法的访问,并将实现和调用分割
继承(c++为例)
数据和普通方法继承情况
class Base {
public:
string name;
Base() { name = "base class"; }
void print() { cout << this->name << endl; }
};
class Sub : public Base {
public:
string name;
Sub() : Base() { name = "sub class"; }
void print() {
cout << name << endl;
cout << Base::name << endl;
Base::print();
}
};
int main() {
Sub obj;
cout << obj.Base::name << endl;
obj.Base::print();
cout << obj.name << endl;
obj.print();
return 0;
}
// 输出
//base class
//base class
//sub class
//sub class
//base class
//base class
- 继承关系中,派生类会直接复制一个基类的数据样本,即使字段相同
- 继承关系中,基类普通方法,直接继承,编译时确定
- 继承关系中,基类虚方法virtual,根据具体派生类覆盖override基类实现,动态绑定
- 基于编译时确定,基类方法无法通过this等访问派生类数据字段和方法
- 基于编译时确定,派生类访问基类数据字段和方法,要明确指出
- Sub继承了Base的print,Base::print在编译时已经确定访问的是Base的name,无法访问Sub的name
- public Base说明了Sub继承的print, name是否可以通过实例在外部访问,换成private编译错误
- Sub中包含重复数据name和方法print时,如果调用基类要用Base::明确指出
多继承
- 多继承的痛点,类关系复杂,优先顺序模糊,多个父类包含同一方法时冲突
- c++解决多继承的问题,要明确指定父类
java单继承多接口
- 实现继承用extends,规格继承用implements,规格接口的实现一般基于组合委托给实现该接口的实例
Mixin
- c++多继承父类有相同名称时要明确指定使用父类,java为了实现多继承采用了接口 正真的实现要通过组合委托,某些情况下可以使用Mixin编程技巧
- Mixin类应该是抽象单一通用的,用于扩展继承者的功能,自身实例化无意义
- python系统级例子ThreadingTCPServer,ThreadingMixIn,ForkingMixIn,TCPServer,UDPServer
# 以序列化字符串为场景,不好的地方,学生和序列化关联不明显
class JsonMixin(object):
"""只考虑int, str, dict, class
"""
def to_json(self):
def help(obj):
result = []
for k, v in obj.items():
if isinstance(v, str):
result.append(f'"{k}":"{v}"')
elif isinstance(v, int):
result.append(f'"{k}":{str(v)}')
elif isinstance(v, dict):
result.append(f'"{k}":{help(v)}')
elif isinstance(v, object):
result.append(f'"{k}":{help(v.__dict__)}')
return "{" + ",".join(result) + "}"
return help(self.__dict__)
class XmlMixin(object):
"""只考虑int, str, dict, class
"""
def to_xml(self):
def help(tag, obj):
result = f"<{tag}>"
for k, v in obj.items():
if isinstance(v, str):
result += f'<{k}>"{v}"</{k}>'
elif isinstance(v, int):
result += f'<{k}>{v}</{k}>'
elif isinstance(v, dict):
result += help(k, v)
elif isinstance(v, object):
result += help(k, v.__dict__)
result += f"</{tag}>"
return result
return help(self.__class__.__name__, self.__dict__)
class Attr(object):
def __init__(self, addr, phone):
self.addr = addr
self.phone = phone
class Student(JsonMixin, XmlMixin):
def __init__(self, name, age, score, attr):
self.name = name
self.age = age
self.score = score
self.attr = attr
swk = Student("孙悟空", 500, {"qi_shi_er_bian": 100, "huo_yan_jin_jing": 95}, Attr("东胜神洲傲来国花果山", "叫你一声爷爷"))
print(swk.to_json())
print(swk.to_xml())
- 不管上面的例子还是之前说的系统级例子,Mixin要通用抽象,首先不能包含太多的数据字段,但功能和数据无关的场景很少,应该说Mixin是扩展符合一定结构的类上
- 如果是继承扩展结构,或方法和数据强依赖,传统的继承更合适
- 由于动态语言传递了self,this等当前实例的指针,很容易将Mixin功能和数据分开
比较
- 基于c++多继承,上面的功能如何实现那,每个父类负责将自己的数据序列化为字符串
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class People {
public:
string name;
int age;
People(string name, int age) : name(name), age(age) {}
string to_json() {
return "{\"name\":\"" + name + "\",\"age\":" + to_string(age) + "}";
}
};
class Attr {
public:
string addr;
string phone;
Attr(string addr, string phone) : addr(addr), phone(phone) {}
string to_json() {
return "{\"addr\":\"" + addr + "\",\"phone\":\"" + phone + "\"}";
}
};
class Score {
public:
string title;
float score;
Score(string title, float score) : title(title), score(score) {}
string to_json() {
return "{\"title\":\"" + title + "\",\"score\":" + to_string(score) + "}";
}
};
class Student : public People, public Attr, public vector<Score> {
public:
Student(string name, int age, string addr, string phone)
: People(name, age), Attr(addr, phone) {}
void add_score(Score &s) { vector<Score>::emplace_back(s); }
string to_json() {
string result = "{\"people\":" + People::to_json() +
",\"attr\":" + Attr::to_json() + ",\"score\":[";
for (auto s = vector<Score>::begin(); s != vector<Score>::end(); ++s) {
result += s->to_json() + ",";
}
result = result.substr(0, result.size() - 1);
result += "]}";
return result;
}
};
int main() {
Student stu("孙悟空", 500, "东胜神洲傲来国花果山", "叫你一声爷爷");
Score boss1("七十二变", 100.0);
Score boss2("火眼金睛", 95.5);
stu.add_score(boss1);
stu.add_score(boss2);
cout << stu.to_json() << endl;
return 0;
}
// 输出
// {"people":{"name":"孙悟空","age":500},"attr":{"addr":"东胜神洲傲来国花果山","phone":"叫你一声爷爷"},"score":[{"title":"七十二变","score":100.000000},{"title":"火眼金睛","score":95.500000}]}
- 如果RTTI支持运行时获取类数据字段,Mixin风格伪代码如下
#include <iostream>
#include <string>
#include <vector>
#include <typeinfo>
using namespace std;
class JsonMixin {
public:
string to_json(void *obj) {
// 编程语言的RTTI或reflection机制如果支持获取到类型以及类字段,这里就可以实现
return "";
}
};
class People {
public:
string name;
int age;
People(string name, int age) : name(name), age(age) {}
};
class Attr {
public:
string addr;
string phone;
Attr(string addr, string phone) : addr(addr), phone(phone) {}
};
class Score {
public:
string title;
float score;
Score(string title, float score) : title(title), score(score) {}
};
class Student : public People,
public Attr,
public vector<Score>,
public JsonMixin {
public:
Student(string name, int age, string addr, string phone)
: People(name, age), Attr(addr, phone) {}
void add_score(Score &s) { vector<Score>::emplace_back(s); }
string to_json() {
// return typeid(*this).name();
return JsonMixin::to_json(this);
}
};
int main() {
Student stu("孙悟空", 500, "东胜神洲傲来国花果山", "叫你一声爷爷");
Score boss1("七十二变", 100.0);
Score boss2("火眼金睛", 95.5);
stu.add_score(boss1);
stu.add_score(boss2);
cout << stu.to_json() << endl;
return 0;
}
补充Mixin维基解释
https://encyclopedia.thefreedictionary.com/mixin
- 在面向对象的编程语言中,Mixin是一个包含供其他类使用的方法的类,而不必是这些其他类的父类,这些其他类如何访问Mixin的方法取决于语言
- Mixin鼓励代码重用,可用于避免多重继承可能导致的继承歧义(钻石问题),或解决语言中对多重继承缺乏支持的问题
- Mixin是一种语言概念,并不一定是语法特性,允许程序员将一些代码注入到类中。 Mixin编程是一种软件开发风格,其中功能单元在一个类中创建,然后与其他类混合
- Mixin类充当父类,包含所需的功能。然后子类可以继承或简单地重用此功能,但不能作为专门化的手段。Mixin会将所需的功能导出到子类,而不会创建严格的、单一的“是一个”关系,这是Mixin和继承概念的重要区别
- 好处
- 它提供了一种多重继承机制,允许一个类使用来自多个类的通用功能,但没有多重继承的复杂语义
- 代码可重用性,当程序员想要在不同的类之间共享功能时,Mixin 很有用。无需一遍又一遍地重复相同的代码,可以简单地将通用功能分组到一个 mixin 中,然后将其包含到每个需要它的类中
- Mixins 只允许继承和使用来自父类的所需特性,而不一定是来自父类的所有特性