制造轮子和创造轮子两者的区别在于:一者为复用,一者为封装
一、与封装的初遇
现在回到第一次我接触封装的时候:
两年前,class这个词进入了我的世界,但class并不是我封装思想的启蒙师。
在此之前,让我初次领略封装的强大之物是电子元件的引脚和它的真值表。
下面的例子希望你可以好好理解一下:怎么在逻辑上实现一位二进制的加法的逻辑运算单元
如果你看不下去,就直接return到第6小点
1.与门(AND)和非门(NOT)
与门和非门.png你觉得很简单?(少年,你对于力量一无所知)
下面的两个东西的组合是计算机逻辑单元的一切
------与门真值表--------------------------非门真值表----------
输入A 输入B 输出Y 输入A 输出Y
0 0 0 0 1
0 1 0 1 0
1 0 0
1 1 1
-------------------------------------------------------------
简单解释一下: A和B你可以看成两根导线,只有高电平和低电平两种形式
1代表高电平,0代表低电平: 对于A B的每次输入,通过非门都会进行输出,
真值表就列出了这个元件的所有输入对应输出的情况表
2.一位加法的逻辑设计
首先我们要明确一位加法的逻辑是什么样的
0 + 0 = 0 0 + 1 = 1 0 + 1 = 1 1 + 1 = 0 (超出长度1溢出)
如果将输入A和B看做加数 ,结果看成Y:可以画出下面的真值表:
--------------------------元件真值表-------------------------
输入A 输入B 输出Y
0 0 0
0 1 1
1 0 1
1 1 0
-------------------------------------------------------------
|--而现在需要做的就是如何通过与门和非门的拼接来形成一个元件
|--使得这个真值表成立,你可以验证一下上面的四种情况:
比如 A B 都输入高电压(即1),来校验一下
上面:非A 得 0 ---> 0 与 B 得 1 ---> 取非得 0
--->0 与 0 得 0 即输出Y是0
下面:非B 得 0 ---> 0 与 A 得 1 ---> 取非得 0
1位加法器.png
这里简单说一下怎么知道要这样画:
注:这里为了方便书写,用位运算的符号表述,~ 代表非 &代表与 |代表或
--------------------------元件真值表-------------------------
输入A 输入B 输出Y
0 0 0
0 1 1
1 0 1
1 1 0
-------------------------------------------------------------
[1].找出Y=1的所有行
[2].写出上步中的逻辑表达式并用或连接 (~A & B) | ( A & ~B)
[3].通过与和非替换掉或: 替换公式 X|Y = ~(~X & ~Y)
~((~(~A & B)) & (~(A & ~B)))
[4].根据与非关系自内向外画图:
|---上行:~A 即上面的 非门A,然后和 B 一起进入或门 ,结果再取非 即:~(~A & B)
|---下行:~B 即下面的 非门B,然后和 A 一起进入或门 ,结果再取非 即:~(A & ~B)
|---上下两行的结果进入或门,之后再入非门 ~((上行结果) & (下行结果))
3.加法器封装
1位加法器.png 1位加法器.png说到这里貌似和封装也搭不上啊?但我已经实现了一个逻辑单元
这个单元可以将两个输入按照1位二进制的逻辑运行,于是封装的价值便体现了
现在将输入的线连起来之后,再套上一个外壳,它便是一个有逻辑价值
独立元件
4.进位器逻辑封装
首先我们要明确进位的逻辑是什么样的
逻辑单元:0 + 0 = 0 0 + 1 = 0 1 + 0 = 0 1 + 1 = 1
--------------------------元件真值表-------------------------
输入A 输入B 输出Z
0 0 0
0 1 0
1 0 0
1 1 1
-------------------------------------------------------------
按照上面的步骤来: A & B 发现原来一个与门就能进行进位操作
加法器.png现在封装的价值就体现了,也就是轮子的价值,可复用,
现在将两个元件进行组装,再加个套子,就能实现更复杂些的逻辑处理单元
5.小结
加法器.png对使用者而言:哥管你里面什么逻辑,我给输入,你给我我想要的输出就行了
确实一个封装体就做到了,隐藏内部的逻辑实现
,将最简洁的使用方式告诉使用者
下面的一幅图和上面的封装体能完成相同的功能,你更用哪个?
--------------元件真值表------------------
输入A 输入B 输出Z 输出Y
0 0 0 0
0 1 0 1
1 0 0 1
1 1 1 0
这是将Z,Y两个输出存顺序排列: 0+0=00 0+1=01 1+0=01 1+1=10
而这个进一位加法器的逻辑单元已经完善了,就可以当做一个元件来使用
形象而简洁地描述一下:
在执行 1 + 1 的时候
高电平经过A,高电平经过B,通过电子元件的内部逻辑单元CRA输出1,通过ADD输出0,
即 Z输出 1,Y输出 0 ,按Z Y进行输出的到了结果 10
IMG20190215193730.jpg 74HC138.png为了更形象说明,这里拿一个
74HC138N
看一下,大概三毛钱一个,
传说中的三八译码器,以前玩单片机做电子钟的时候用到过,现在差不多忘完了...
在此强调一点:电子元件内部是封装符合真值表的逻辑单元
电子元件都有输入和输出,即输入+ 逻辑单元处理 ----> 希望的输出
至此为止,不再延伸,有兴趣的自己玩...
6. 升华
我们针对
输入
和输出
暴露引脚
,将逻辑单元
封装其中
这样对于指输入就会有期望的输出,在便是逻辑单元的封装
它在软件领域有一个俗称:轮子,这里暂时称为封装体
封装体的优越之处:(只要封装体能实现预期的功能,那么:)
|--无论时间,空间的变化,你的输入都会变成你期望的输出
|--这便具有可复用性,再需要它时便无需再次设计
|--隐藏内部的逻辑实现,以保护封装体的内部封装不被破坏
|--仅暴露接口提供输入和输出,简化使用方式
下面关于封装做一个类比:
-- | 电子元件 | 电脑 | 开源类库 | 人 |
---|---|---|---|---|
封装物 | 硬质外壳 | 塑料/金属外壳 | .jar,.so包等 | 躯壳 |
接口 | 引脚 | 键盘,USB,电源键等 | api方法 | 口 ,耳,眼,鼻,皮肤 |
输入 | 高低电平 | 键盘输入,U盘头 | 方法调用 | 食物,音乐,书籍,气味,触摸 |
输出 | 运算结果 | 屏幕显示,U盘信息 | 运算结果 | 能量,思想,劳动力,汗液等 |
处理核心 | 逻辑单元 | CPU | 类 | 大脑 |
使用前提 | 真值表 | 说明书 | API文档 | 不可描述(宇宙造物手册?) |
核心组成元 | 与门+非们 | 各种电子元件 | 属性+方法 | 脱氧核糖核酸 |
7.封装没有缺点吗?
我还没发现世上有什么东西没有缺点
就连我们这么伟大的人类都可能发生基因突变,更何况是人类制造的封装体
对于电子元件来说,由于环境因素,人为因素可能导致元件内部的损坏,
而无法拆封的你只能选择更换一个封装体,这就造成了浪费,虽然在我们眼里不算什么
对于一个开源框架来说,一个bug可能导致所有使用者的崩溃,这是很严重的
也就是使用一个封装体是具有一定的风险性的,当然大厂的框架会相对完善
再者就是接口的复杂,有种必须按照别人意志去做的压迫感
没有真值表和接口图,使用的人来说是灾难,这种感觉就像...
必须使用的类库没有API文档,并且方法命名为a(),b(),c()一样让人抓狂
可惜对于人类,宇宙并没有留下一份API文档,一切都要靠我们自己来编写...
好了,引入完成,下面进入正文
二、编程中初遇封装
1.与class的初遇
两年前,一开始class 以及它 的 private 是我非常难理解的
对类的认识是在C++里,印象最深的是圆这个类,从获取圆的面积开始
---->[头文件]-------------------
#ifndef BSAE_CIRCLE_H
#define BSAE_CIRCLE_H
class Circle {
const double PI = 3.141592654;
public:
double getArea();
double getRadius() const;
void setRadius(double radius);
private:
double mRadius;
};
#endif //BSAE_CIRCLE_H
---->[cpp文件]-------------------
#include "Circle.h"
double Circle::getArea() {
return mRadius * mRadius * PI;
}
double Circle::getRadius() const {
return mRadius;
}
void Circle::setRadius(int radius) {
mRadius = radius;
}
---->使用类----------------
#include <iostream>
#include "circle/Circle.h"
int main() {
Circle circle;
circle.setRadius(10);
std::cout << circle.getArea() << std::endl;
return 0;
}
2.现在来看
一个类便是类的设计者对于某种概念的封装
我理解类存在的意义确实费了不少时间,当时疑问:
为什么一行代码解决的事要拆成一个类?而且又是头又是cpp的
现在发现有这种疑问的根源在于当时没有认清自己的角色
认清自己的角色,这对入门的人来说是非常困难的,类的本身就是一个逻辑处理单元
而程序员的角色是设计类的人,就像电子元件的设计者在设计逻辑单元一样
但任何一个程序员都必定是第一个使用者,所以两个角色在同一个人身上
对于入门的来说,只能是一个使用者,因为你只是在意获取结果,而没有程序员的设计之魂
就会感觉很混乱,站在一个使用者的角度,类确实将半径为10的圆面积这个问题变得复杂了
关系.png
内部分析.png但如果封装的思想到位,就可以明确这个类的价值
setRadius进行输入,getRadius,getArea进行输出,其中的一切都是逻辑单元
封装.png如果你还不清楚,看下图:这就是封装
看不见内部了,该怎么用? 电子元件有真值表,类有API文档
至此我想对于类和封装的关系应该讲的淋漓尽致了
----------Circle API--------------------------
double getArea(); 获取圆的面积
double getRadius(); 获取圆的半径
void setRadius(double radius); 设置圆的半径
-----------------------------------------------
当你在非Circle中使用的时候,你的角色就变成了Circle元件的用户
你需要关注的就只用它的输入和输出,内部逻辑实现已经不需要你考虑了
不过一旦你进入了Circle中,你的角色就是Creator,你可以把自己当成造物主来实现逻辑单元
Circle circle;
circle.setRadius(10);
circle.getArea()//314.159
网友评论