随笔编程杂谈录:[-封装-]

制造轮子和创造轮子两者的区别在于:一者为复用,一者为封装

一、与封装的初遇

现在回到第一次我接触封装的时候:

两年前,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位二进制的逻辑运行,于是封装的价值便体现了
现在将输入的线连起来之后,再套上一个外壳,它便是一个有逻辑价值独立元件

1位加法器.png
1位加法器.png

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  

为了更形象说明,这里拿一个74HC138N看一下,大概三毛钱一个,
传说中的三八译码器,以前玩单片机做电子钟的时候用到过,现在差不多忘完了...

IMG20190215193730.jpg
74HC138.png

在此强调一点:电子元件内部是封装符合真值表的逻辑单元
电子元件都有输入和输出,即输入+ 逻辑单元处理 ----> 希望的输出
至此为止,不再延伸,有兴趣的自己玩...


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 
#include "circle/Circle.h"

int main() {
    Circle circle;
    circle.setRadius(10);
    std::cout << circle.getArea() << std::endl;
    return 0;
}


2.现在来看

一个类便是类的设计者对于某种概念的封装
我理解类存在的意义确实费了不少时间,当时疑问:
为什么一行代码解决的事要拆成一个类?而且又是头又是cpp的

现在发现有这种疑问的根源在于当时没有认清自己的角色  
认清自己的角色,这对入门的人来说是非常困难的,类的本身就是一个逻辑处理单元
而程序员的角色是设计类的人,就像电子元件的设计者在设计逻辑单元一样  

但任何一个程序员都必定是第一个使用者,所以两个角色在同一个人身上 
对于入门的来说,只能是一个使用者,因为你只是在意获取结果,而没有程序员的设计之魂
就会感觉很混乱,站在一个使用者的角度,类确实将半径为10的圆面积这个问题变得复杂了  
关系.png

但如果封装的思想到位,就可以明确这个类的价值
setRadius进行输入,getRadius,getArea进行输出,其中的一切都是逻辑单元

内部分析.png

如果你还不清楚,看下图:这就是封装
看不见内部了,该怎么用? 电子元件有真值表,类有API文档
至此我想对于类和封装的关系应该讲的淋漓尽致了

封装.png
----------Circle API--------------------------
double getArea(); 获取圆的面积
double getRadius(); 获取圆的半径
void setRadius(double radius); 设置圆的半径
-----------------------------------------------

当你在非Circle中使用的时候,你的角色就变成了Circle元件的用户
你需要关注的就只用它的输入和输出,内部逻辑实现已经不需要你考虑了
不过一旦你进入了Circle中,你的角色就是Creator,你可以把自己当成造物主来实现逻辑单元

Circle circle;
circle.setRadius(10);
circle.getArea()//314.159

你可能感兴趣的:(随笔编程杂谈录:[-封装-])