架构漫谈系列(2) 封装(Encapsulation)
这是这个系列的第二篇。在第二篇里,我决定讲一讲封装。
程序的不同部分应该用封装去互相隔离,模块之间应该不应该产生很随意的关联。
可能有的人觉得不解,又或觉得是有道理的废话,不急,先一步一步来。
我们先来看看面向对象的三个基本特征是什么?
- 继承
- 多态
- 封装
如果你是科班毕业,这6个字应该是你第一次学到类(class)的时候就听老师说了。
我们老师的话大概是这样的:
在类里面,封装就是通过一些手段来限制类外部的访问,依此隔离出类相对封闭的区域。
也就是说,如果有人想要操作类里面的成员(field),不应该让它直接进行这样操作。而应该通过良好定义的函数(或属性的Setter)来完成。除非你有不得不如此的理由,否则就不应该让人家直接访问你的私有成员。
下面的代码通常是bad practice。
任意的类均能任意的修改Person内的Name和Age,即便Name写成乱码或将Age设成负数,都是可以做到的,Person类自己是控制不住的。
public class Person
{
public int Age;
public string Name;
}
下面的代码则是演示的使用Setter或函数来控制name和age的值,防止错误的值被录入。
public class Person
{
private int _age;
private string _name;
public int Age
{
get => _age;
set
{
if (value >= 0 && value <= 200 && value != _age)
_age = value;
}
}
public void SetName(string newName)
{
if (!string.IsNullOrWhiteSpace(newName))
_name = newName;
}
}
上面的例子描述了class对外的封装。
同样道理,程序的模块之间也是如此,模块之间不应该任意的暴露内容出来,而应该通过良好定义的接口来实现模块之间的协作。
这种封装的设计方式隔离了模块内部的设计,只要模块的对外接口不产生变化,模块内部的任意变化都不会对模块间协作造成影响,从而实现子系统间的隔离。
实例
假如我们现在要设计一个系统,你可以简单的理解成某个在线商城的发货的或物流子系统。
这个子系统需要完成几个基本的功能:
- 将货物寄送出去(发货),这是最基本的功能
- 其他的子系统需要一个API来查询当前的寄送的进度
- 发货后,需要得到发货的运输船的信息
- 如果客户临时又取消了订单,则依据具体的寄送进度通知对应的运输船取消订单
- 后来业务扩大了,我们可能需要考虑增加其他的运输方式,例如空运。
我们首先分析一下,应该如何来完成这个功能。
首先,我们需要一个运输中心(ShippingCenter),使用这个运输中心,可以寄送本公司任意的产品(Product)。
我们需要返回运输船的信息,为了更好的表达我的想法,这里理解成要返回运输船的实例。
我们的运输船能直接告诉我们当前运输的状态,如果在运输前需要召回任何产品,我们的运输船会安排人自动的处理。
所以伪代码大概就长这个样子,ShippingCenter能运输货物,并返回运送这个货物的运输船实例,然后运输船本身有函数和属性来查看状态和召回货物。
也就是说,这是对外的最基本的接口:
class ShippingCenter{
Steamship Ship(Product p);
}
class Steamship{
ShipingProgress DeliveryProgress{get;}
void Recall(Product p);
}
可是,以后业务的扩张,增加了空运,那怎么办呢?
假如ShippingCenter不要返回SteamShip的具体类就好了。
那么,不如我们增加一个接口吧,比如,就叫IVehicle好了,其实vehicle也是个抽象的概念,我在iciba上查到vehicle的含义是『交通工具』。
慢……这里再引申出一个知识点:抽象类。
抽象类到底是个啥?
我在面试的时候有时候会问到这个问题,好多同学给出的答案就是abstract,里面不能写具体的实现。
我得补充一下,如果将抽象类跟现实的生活联系起来,这样就可以更好的理解。
抽象类就是包含的信息还不够具体化,于是就只能是抽象类。
举个例子,刚刚说的vehicle:交通工具。
如果你要写个类,叫做交通工具,这个就应该写成抽象类。
为什么呢?
因为交通工具这个概念本来就是抽象的概念,我们常常接触到的交通工具就有:
自行车、摩托车、三轮车
小轿车、公共汽车、卡车
地铁
飞机
轮船
所以说,今天早上小王问你今天怎么来上班的,你的回答是,我开车来的,或者是我坐公交、地铁来的。
你肯定不会回答说:『我是坐交通工具来的。』,如果你这样回答,小王会很懵逼的。
与此类似的概念比如哺乳动物、形状、玩具、机械装置等等,当有人跟你提到这些词时,你的脑海里是无法浮现出它的具体形象的,你只知道,他们属于某个类别。
扯了一段闲话,转回正题。
我们的运输中心,需要一个抽象的概念:那么,用接口还是抽象类呢?
这里暂时不扯这个问题了,不然离题越来越远。
今天在这里就选用接口好了(好像没有按常理出牌啊~~~)。
所以,这个系统大致变成了这个样子:
class ShippingCenter{
IVehicle Ship(Product p);
}
class Steamship : IVehicle{
ShipingProgress DeliveryProgress{get;}
void Recall(Product p);
}
可能这里还是有点不够形象,我先放出整段的代码。
public class ShippingCenter
{
public static IVehicle Ship(Product productToDeliver)
{
var vehicle = GetIVehicle();
Internalivery(vehicle, productToDeliver);
return vehicle;
}
private static IVehicle GetIVehicle()
{
if (DateTime.Now.Millisecond % 2 == 1)
{
return new AirliftPlane();
}
return new Steamship();
}
private static void Internalivery(IVehicle vehicle, Product delivery)
{
// some logic to deliver the Product.
}
}
public enum ShippingProgress
{
Preparing,
Shipping,
Deliveryed,
}
public interface IVehicle
{
ShippingProgress DeliveryProgress { get; }
void Recall(Product delivery);
}
public class Product
{
public Product(string name, int weight)
{
Name = name;
Weight = weight;
}
public string Name { get; }
public int Weight { get; }
}
internal class AirliftPlane : IVehicle
{
public ShippingProgress DeliveryProgress => ShippingProgress.Preparing;
public void Recall(Product delivery)
{
// recall product if the Shipping Progress is still ShippingProgress.Preparing
}
}
internal class Steamship : IVehicle
{
public ShippingProgress DeliveryProgress => ShippingProgress.Deliveryed;
public void Recall(Product delivery)
{
// if it's already devivered, maybe recalling is not allowed anymore.
// or some other business logic.
}
}
外部调用时,就像这样:
var p1 = new Product("iPad", 15);
var vehicle = ShippingCenter.Ship(p1);
Console.WriteLine($"current ship status {vehicle.DeliveryProgress}");
Console.WriteLine("now I want to cancel it.");
vehicle.Recall(p1);
var p2 = new Product("Books", 123);
vehicle = ShippingCenter.Ship(p2);
vehicle.Recall(p2);
其他的子系统只需要调用ShippingCenter.Ship()函数,然后返回一个IVehicle的接口,这个接口上可以调用DeliveryProgess的属性来获取当前的运输状态,也可以调用vehicle上的Recall()函数来召回产品。
所以,该系统的外部接口是
ShippingCenter.Ship()
IVehicle.DeliveryProgress
IVehicle.Recall()
这就回到本文最初的话题:子系统内部的封装。对外只有这三个接口。只要我的对外接口没变,其他的都不是问题。
所以,我要加空运、陆运、太空运都是系统内部的事情,我们内部的事情我们自己处理,你们统统不要管,你管的太多,你的脑子会乱掉的。所以,安安心心的交给我们物流子系统处理就好了。
所以,再进行分层和架构的时候,妥善的考虑对外的接口是很有必要的。如果你的代码位于不同的程序集,考虑更多的使用internal关键字,切勿动不动就是public。
如果你的类用上了public,那么其他开发者自然就可能调用到你的public函数,如果他们调用这些函数很多,你的子系统就不够独立,更像是一种千丝万缕的复杂网状关系了。
由于代码很简单,也不需要做过多的解释,但也稍微提一下:
- ShippingProgress是个枚举,表示可能的运输状态
- Product表示我们要运输的产品
- AirliftPlane表示我们以后的空运方式
- Steamship就是我们的水运方式
- ShippingCenter就是我们的运输中心,它可以Ship产品,也能以接口方式返回当前运输方式的那个实例。GetIVechicle()模拟一种运输方式的选择,比如这个产品中含有电池,可能不能空运;这个是加急件,首先空运等等,但是为了简化动作,我只是取模而已,这不是本文的重点。
- IVehicle上能返回ShippingProgress状态,也能召回产品。
至此,封装部分到此为止,记住,勿滥用public关键字哦。
小春 原创内容 本文地址:http://www.cnblogs.com/asis/p/architecture-encapsulation.html https://1few.com/architecture-encapsulation/ |
---|
架构漫谈系列(2) 封装(Encapsulation)的更多相关文章
- 读<架构漫谈>系列有感
读了这一系列博文,我对架构也有了大致的了解.在简单的阅读之后,我解决了几个问题. 第一个问题,什么是架构? 要学习架构,首先要知道架构.那么,什么是架构呢?引用<架构漫谈(一)>里的话就是 ...
- 【PC网站前端架构探讨系列】关于中小型PC网站前端架构方案的讨论与实践
目 录 1.遇到的问题 2.目标 3.探讨 4.架构设想 5.流程 6.初步实现 7.存在问题 8.最后 遇到的问题 我在这个系列上篇文章 已经讲解并开始逐步应用模块化思想,不知大家还记不记得,题 ...
- Nebula 架构剖析系列(一)图数据库的存储设计
摘要 在讨论某个数据库时,存储 ( Storage ) 和计算 ( Query Engine ) 通常是讨论的热点,也是爱好者们了解某个数据库不可或缺的部分.每个数据库都有其独有的存储.计算方式,今天 ...
- 读架构漫谈&我眼中的架构师
本周是开学的第二周,读了由资深架构师王概凯 Kevin 执笔的系列专栏架构漫谈.初识这门课,懂得也不是很多,读了架构漫谈,有了一些理解. 首先作者讲述了缘起,由早期人独立自主生活到后来的集群,作者由这 ...
- mvc项目架构分享系列之架构搭建初步
mvc项目架构分享系列之架构搭建初步 Contents 系列一[架构概览] 0.项目简介 1.项目解决方案分层方案 2.所用到的技术 3.项目引用关系 系列二[架构搭建初步] 4.项目架构各部分解析 ...
- Asp.net mvc项目架构分享系列之架构概览
Asp.net mvc项目架构分享系列之架构概览 Contents 系列一[架构概览] 0.项目简介 1.项目解决方案分层方案 2.所用到的技术 3.项目引用关系 系列二[架构搭建初步] 4.项目架构 ...
- Asp.net mvc项目架构分享系列之架构搭建初步
copy to:http://www.cnblogs.com/ben121011/p/5014795.html 项目架构各部分解析 Core Models IDAL MSSQLDAL IBLL BLL ...
- Junit4 架构设计系列(2): Runner.run()与Statement
Overall 系列入口: Junit4 架构设计系列(1): Request,ClassRequest 和 RunnerBuilder 前文中,我们基本理清了Junit4执行Case大体上的Flow ...
- Junit4 架构设计系列(1): Request,ClassRequest 和 RunnerBuilder
Overall Junit的成功已不言而喻,其广泛应用于单元测试,测试驱动开发领域.大量的工具,IDE都集成了JUnit,著名的有Maven,Ant,Eclipse,甚至像Google SDK提供的A ...
随机推荐
- luogu P1007 独木桥
序:难度标签是普及-,便觉得应该非常简单,结果发现有一个弯半天没绕过来,所以认为这道题对于第一次做的人来讲还是很是比较有意义的. 题目描述: 长度为len的桥上有n个士兵,你不知道他们的初始方向.已知 ...
- python基本语法-加密解密等
1. 编写函数,要求输入x与y,返回x和y的平方差 2. 计算1到100的平方的和 3. 编写函数,若输入为小于100的数,返回TRUE,大于100的数,返回FALSE 4. 某个公司采用公用电话传递 ...
- eclipse简介及下载
一.Eclipse 是非常着名的跨平台的自由集成开发环境(IDE).最初主要用来Java语言开发,但是目前亦有人通过插件使其作为其他计算机语言比如C++和Python的开发工具. 二.Eclipse的 ...
- linux下归档、解压缩工具:tar命令
tar是一个类似于windows下的解压缩工具,可以将一大堆文件或目录打包成一个文件,还可以通过特定选项使用压缩工具进行解压缩. 语法: tar (选项) (参数) 常用选项: -c:创建打包文件. ...
- hdu4099 Revenge of Fibonacci 字典树
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4099 思想很容易想到 就是预处理出前10w个的fib数,然后建树查询 建树时只用前40位即可,所以在计 ...
- Android ec环境配置
ec环境配置 1.0概述 鉴于很多同事,或者新从事android开发,虽然会做android的开发,但是会遇见一些最基本的环境搭建问题,本文仅作为(win7 64位系统)eclipse中集成andro ...
- webpack的多文件打包问题
1.第三方库如vue,vue-router可以利用webpack中的entry指定vendor:['vue','vue-router']来打包在一个文件中 2.将这些文件单独提取出来,在页面中使用&l ...
- html学习笔记 - 标签
单标签 : <!DOCTYPE html> 解析类型标签 <!-- xxx --> 注释标签 <br /> 换行标签 <hr /> 分割线标签 < ...
- 教育类APP开发现新增长,多款APP该如何突围?
"十二五"以来,国家共出台相关的重大教育政策文件741个,而进入到"十三五"时期教育领域综合改革深入推进的关键期,不断促进教育现代化的实现.加快迈入人力资源强国 ...
- tomcat产生大量TIME_WAIT连接
http://blog.csdn.net/jiangguilong2000/article/details/12523771