封装 (面向对象编程)

面向对象编程方法中,封装(英语:Encapsulation)是指,一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。同时,它也是一种防止外界调用端,去存取物件内部实现细节的手段,这个手段是由编程语言本身来提供的。封装被视为是面向对象的四项原则之一。

封装,一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法

适当的封装,可以将物件使用接口的程序实现部分隐藏起来,不让用户看到,同时确保用户无法任意更改物件内部的重要资料,若想接触资料只能通过公开接入方法(Publicly accessible methods)的方式( 如:"getters" 和"setters")。它可以让代码更容易理解与维护,也加强了代码的安全性。[来源请求]


解释

在面向对象的语言里,封装往往指以下两个相关联但是独立的概念,有时候这两者是存在因果关系。[1][2]

  1. 一种编程语言的机制,限制直接访问某些对象的部件。[3][4]
  2. 一种编程语言的结构体,其将数据和操作该数据的方法绑在一起,提供了便利性。[5][6]

一些编程语言的研究者和学者将定义①或者定义①+②作为辨认一门语言是否为面向对象语言的标准之一。一些编程语言提供了闭包作为封装,但是这种功能不属于面向对象的范畴。

在许多编程语言里,组件并不会自动隐藏并且能够被重写,因此,一些倾向于定义②的人会将资讯隐藏(information hiding)作为一个单独的定义③列举出来。

在使用类的大多面向对象的编程语言中,虽然封装是被支持的,但是仍有其他替代品可以选择。

封装和继承

《Design Patterns》的作者们曾经大篇幅地讨论封装和继承的矛盾。根据他们自身的经验,设计师们滥用继承。他们认为继承将破坏封装,考虑父类的实现细节将暴露给子类。[7]

父类的内部实现对于子类来说是不透明的(实现一个子类时, 你需要了解父类的实现细节, 以此决定是否需要重写某个方法)。[8]同时,一旦父类被修改,因为子类依赖着父类,所以子类的实现也需要被重新审视。

资讯隐藏

封装可以隐藏成员变量以及成员函数,对象的内部实现通常被隐藏,并用定义代替。举个例子,仅仅对象自身的方法能够直接接触或者操作这些成员变量。隐藏对象内部资讯能供保证一致性:当用户擅自修改内部部件的数据,这可能造成内部状态不一致或者不可用;隐藏对象内部资讯能阻止这种后果。一个众所周知的好处是,降低系统的复杂度和提高健壮性

大多数语言(如:C++、C#、 Delphi、Java)通过设定等级去控制内部资讯隐藏,经典的是通过保留字 public 暴露资讯和 private隐藏资讯。一些语言(如: SmalltalkRuby )只允许对象去访问隐藏资讯。

通常,也是存在方法去暴露隐藏资讯,如通过反射(Ruby、Java、C#、etc.)或名字修饰(Python)。

程序示例

C#示例

这是一段C#代码,演示了如何使用private关键字限制变量的访问:

namespace Encapsulation 
{
	class Program 
	{
		public class Account 
		{
			private decimal accountBalance = 500.00m;

			public decimal CheckBalance() 
			{
				return accountBalance;
			}
		}

		static void Main() 
		{
			Account myAccount = new Account();
			decimal myBalance = myAccount.CheckBalance();

			/* Main方法能够通过public的“CheckBalance”方法确认账户余额,但是不能更改它 */
		}
	}
}

JAVA示例

下面是Java的演示程序:

public class Employee {
    private BigDecimal salary = new BigDecimal(50000.00);
    
    public BigDecimal getSalary() {
        return salary;
    }

    public static void main() {
        Employee e = new Employee();
        BigDecimal sal = e.getSalary();
    }
}

名字修饰(Name mangling)

下面是Python的要给实例,Python并不支持隐藏变量。然而约定俗成,_var 形式的变量被认为是私有变量。

class Car: 
    def __init__(self):
        self._maxspeed = 200
 
    def drive(self):
        print(f'maximum speed is {self._maxspeed}')
 
redcar = Car()
redcar.drive()  # 打印 'maximum speed is 200'

redcar._maxspeed = 10
redcar.drive()  # 打印 'maximum speed is 10'

参考文献

  1. ^ Scott, Michael Lee. Programming language pragmatics 2. Morgan Kaufmann. 2006: 481. ISBN 978-0-12-633951-2. Encapsulation mechanisms enable the programmer to group data and the subroutines that operate on them together in one place, and to hide irrelevant details from the users of an abstraction. 
  2. ^ Dale, Nell B.; Weems, Chip. Programming and problem solving with Java 2nd. Jones & Bartlett. 2007: 396. ISBN 978-0-7637-3402-2. 
  3. ^ Mitchell, John C. Concepts in programming languages. Cambridge University Press. 2003: 522. ISBN 978-0-521-78098-8. 
  4. ^ Pierce, Benjamin. Types and Programming Languages. MIT Press. 2002: 266. ISBN 978-0-262-16209-8. 
  5. ^ Rogers, Wm. Paul. Encapsulation is not information hiding. JavaWorld. 18 May 2001 [2019-11-22]. (原始内容存档于2013-10-29). 
  6. ^ Connolly, Thomas M.; Begg, Carolyn E. Ch. 25: Introduction to Object DMBS § Object-oriented concepts. Database systems: a practical approach to design, implementation, and management 4th. Pearson Education. 2005: 814. ISBN 978-0-321-21025-8. 
  7. ^ Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns. Addison-Wesley. 1994. ISBN 978-0-201-63361-0. 
  8. ^ 萧萧. 怎样理解“组合优于继承”以及“OO的反模块化”,在这些方面FP具体来说有什么优势?. 2017-06-09 [2019-11-22]. (原始内容存档于2020-02-04) (中文(简体)).