深入分析面向对象中的封装作用

深入分析面向对象中的封装作用

之前我(梦在旅途)发表过一篇名为《深入分析面向对象中的对象概念》的文章,里面主要讲解了类与对象的关系以及对象的概念、状态、行为、角色几个知识点,让大家对对象有了一些深入的了解,而本文则再来谈谈面向对象的三大特性之一:封装,封装是实现面向对象的基础,深入的理解封装的特性,有利于我们能更好的领悟面向对象的思想以及实现面向对象编程。以下均为本人凭借多年开发经验总结的个人观点,大家若发现有误或不足的地方,欢迎指正和交流,谢谢!

一、什么是封装?

顾名思义,封:封闭,装:装起来,将具体的实现细节装到一个容器中,并封闭起来,防止容器外部直接访问容器内部的实现细节,仅公开暴露指定的访问路径;生活中的例子:到饭馆吃饭,你只需要跟服务员讲你要吃什么饭菜,服务员就会跟后台厨房的厨师说明一下,等待一段时间后,饭菜就被服务员直接递送到你面前,作为客户的你是不知道也无需知道饭菜的具体做法,后台厨房的厨师及做饭菜的过程就是被封装的细节,而服务员则是公开暴露的访问路径,你只能通过服务员点餐然后获得饭菜,而不能直接去到后台厨房要求厨师如何做饭菜;

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
     /// <summary>
     /// 饭馆
     /// </summary>
     class  FanGuan
     {
         private  string  doFoodName;
 
         /// <summary>
         /// 点餐
         /// </summary>
         /// <param name="foodName"></param>
         public  void  ChooseFood( string  customer,  string  foodName)
         {
             doFoodName = foodName;
             Console.WriteLine( "顾客:{0},点餐:{1}" , customer, foodName);
         }
 
         /// <summary>
         /// 获得饭菜
         /// </summary>
         /// <returns></returns>
         public  string  GetFood()
         {
             string  cookeResult = CookeFood( "厨师甲" );
             return  string .Format( "{0},请您用餐,谢谢!" , cookeResult);
         }
 
         /// <summary>
         /// 厨师做菜,私有方法,外部不可访问
         /// </summary>
         /// <param name="cooker"></param>
         /// <returns></returns>
         private  string  CookeFood( string  cooker)
         {
             Console.WriteLine( "厨师:{0}开始做菜:{1}>>>>" , cooker, doFoodName);
             Console.WriteLine( "开火" );
             Console.WriteLine( "放油" );
             Console.WriteLine( "放食材,翻炒" );
             Console.WriteLine( "加入佐料" );
             Console.WriteLine( "菜熟起锅,盛到盘子递给服务员" );
             Console.WriteLine( "结束<<<<" );
 
             return  string .Format( "菜:{0}已做好" , doFoodName);
         }
     }
 
 
//实际用法:
         static  void  Main( string [] args)
         {
             FanGuan fanGuan =  new  FanGuan();
             fanGuan.ChooseFood( "梦在旅途" "红烧茄子" );
             string  food = fanGuan.GetFood();
             Console.WriteLine(food);
             Console.WriteLine( "用餐" );
 
             Console.ReadKey();
         }

该示例非常简单,演示结果就不再截图出来了。

二、封装的作用是什么?

1.隔离性:
被封装后的对象(这里的对象是泛指代码的编程单元,一般指:程序集,命名空间,类,方法,属性,变量等)其外部对象是无法直接访问对象的内部实现细节,内部实现细节的的改动不会影响到外部对象的访问原则(即:对象内部修改后,在公开暴露指定的访问路径不变的情况下,外部访问它的对象是无需修改的),这是隔离性的体现,同时也是实现高内聚,低耦合的最根本的思想之一;
2.可复用性:
被封装后的对象可以被外部多个对象访问,而无需为每个外部对象去指定不同的服务对象;如:所有的对象的基类都是object类,object类里面的公共成员可以被其所有子类使用,Ado.Net相关的数据访问类及其公共成员均可被其它所有的对象使用等。
3.可读性:
被封装后的对象的名称(如:程序集名,类名,方法名)如果命名恰当,那么就能在不看里面的实现细节的前提下,了解该对象的作用;如:DataTable就是用来装表格数据的;ToString就是转换为字符串,Length就是指长度等。

三、封装的范围有哪些?

1.封装成常量/变量:
如:计算圆周长度,未封装前的代码如下:

1
2
3
//封装前:
decimal  result = 2 * 3.141592653M * 10.8M;
Console.WriteLine( "圆周长度是:{0}" , result);

封装后的代码如下:

1
2
3
4
5
6
7
//封装后:
const  decimal  PI = 3.141592653M;
decimal  radius = 10.8M;
 
decimal  circumference = 2 * PI * radius;
 
Console.WriteLine( "圆周长度是:{0}" , circumference);

你觉得哪种可读性更高一些呢?从我看来,很显然封装后的代码更易被他人所理解,因为圆周长的计算公式就是:C=2πr;从circumference就知道是圆周长的结果,而等号右边刚好符合圆周长计算公式,所以非常的直观,可读性由此体现出来;
2.封装成方法/函数/属性:

1
2
3
4
5
6
7
8
9
10
//计算圆周长
static  decimal  ComputeCircumference( decimal  radius)
         {
             const  decimal  PI = 3.141592653M;
             return  2 * PI * radius;
         }
 
 
//用法:
Console.WriteLine( "圆周长度是:{0}" , ComputeCircumference(10.8M));

通过封装成方法后,我们看到ComputeCircumference方法,就知道是计算圆周长,同时我可以用此方法来计算所有的不同半径的圆的周长,可读性、复用性由此体现出来;
3.封装成类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
     /// <summary>
     /// 圆类
     /// </summary>
     class  Circle
     {
         //原点X坐标
         public  int  OriginX
         get set ; }
 
         //原点Y坐标
         public  int  OriginY
         get set ; }
 
         //半径
         public  decimal  Radius
         get set ; }
 
         public  Circle( int  originX,  int  originY,  decimal  radius)
         {
             this .OriginX = originX;
             this .OriginY = OriginY;
             this .Radius = radius;
         }
 
         /// <summary>
         /// 获取圆周长度
         /// </summary>
         /// <returns></returns>
         public  decimal  GetCircumference()
         {
             const  decimal  PI = 3.141592653M;
             return  2 * PI *  this .Radius;
         }
     }
 
 
//用法:
Circle circle =  new  Circle(10,10,10.8M);
Console.WriteLine( "圆周长度是:{0}" , circle.GetCircumference());

从上述示例代码可以看出,我定义(封装)了一个圆类,圆类有原点及半径,同时有一个获取圆周长度的方法,该圆类可以用来表示多个不周大小不同位置的圆,而且都能获得圆的圆周长,至于圆周长是如何计算的,PI的精度是多少,我们无需知道也无法直接更改,故隔离性、可读性、复用性都体现出来了;
4.封装成层/包/程序集:

有的时候因系统架构的需要,我们可能需要将描述各种图形类信息的代码单独封装成一个程序集、包、命名空间,以便于代码的管理,于是我们可以将上述Circle类放到一个单独的程序集中,同时程序集及命名空间名称定为:Math.Shape,意为数学.图形,从名字就知道这个程序集或命名空间下都是用来处理数学与图形相关的。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
namespace  Math.Shape
{
      public  class  Circle
      {
          //省略,同上
       }
 
}
 
//用法:
Math.Shape.Circle circle =  new  Math.Shape.Circle(10, 10, 10.8M);
Console.WriteLine( "圆周长度是:{0}" , circle.GetCircumference());

四、封装的禁忌

1. 忌封装过度

如:

1
2
3
4
5
6
7
string  a =  "a" ;
string  b =  "b" ;
string  c =  "c" ;
string  d =  "d" ;
string  joinString = a + b + c + d;
 
Console.WriteLine(joinString);

改进后的代码:

1
2
string  joinString =  "{0}{1}{2}{3}" ;
joinString =  string .Format(joinString,  "a" "b" "c" "d" );

这是典型的封装过度,太过原子化,为每一个字符串都定义一个变量,代码量增加,且效率也不高,而改进后代码精简且效率高。

当然还有一些封装过度,比如:一个方法或一个类的代码量非常多,假设有一个数学计算类,可以计算所有的数字类型和所有的数学计算方法,想象一下它的代码量会有多少,这个时候就应该考虑进行适当的拆分封装,至少可以拆成数学类型类及数学计算类。
2. 忌不恰当的封装

如:

1
2
3
4
5
6
7
8
9
static  bool  IsNullOrEmpty( string  str)
{
     return  string .IsNullOrEmpty(str);
}
 
static  bool  IsNotNullOrEmpty( string  str)
{
     return  ! string .IsNullOrEmpty(str);
}

从上述代码可以看出,String的IsNullOrEmpty已经可以满足需求,但有些人可能还会画蛇添足,增加这么两个类,而即使是为了想不写string.这样的,那也没有必需写两个方法,一个方法可以了,因为这两个方法本身就是对立的,只可能同时存在一种情况,可以进行如下改进:

1
2
3
4
5
6
7
8
9
static  bool  IsNullOrEmpty( object  obj)
{
     if  (obj ==  null )
     {
         return  true ;
     }
 
     return  string .IsNullOrEmpty(obj.ToString());
}

这样改进后,明显的IsNullOrEmpty可以用来判断所有的类型是否为Null 或者 Empty,如果需要判断不需要为Null 或者 Empty,只需调用该方法并取反即可,如:!IsNullOrEmpty("zuowenjun")

五、结尾

这篇文章本来打算自去年发布了《深入分析面向对象中的对象概念》后就立即写这篇,我一般写一些总结性很强的文章都是先在WORD中写好后再COPY过来的,这篇文章同样也是,但由于之前工作原因一直是只写了一个提纲,故今天看到了这篇博文躺在我的文件夹中,同时又联想最近我当面试官及新进人员的状况(我发表过一篇文章《由面试引发的思考:B/S与C/S究竟是何物》),于是果断在今天花了一个下午的时间,边想边写,终于给写完了,也希望大家能够从中受益,这两篇文章本身没有很深很新的技术,但作为一个程序员,若想技术上有所造诣,必需先学好基本功,我再重复一下我的观点:

技术就如同武术,基本功很重要,只要基本功扎实了,再去学习架构、设计模式,就会比较容易,同时这些看似高大上的东西,如:AOP,SOA,DI,IOC,DDD,CQRS等,只要明白其原理,举一反三就能达到“无招胜有招”的最高境界。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值