面向对象的世界引入了对象的概念,而这些对象又具有属性属性(数据成员) 和过程(成员函数)。这些函数的作用就是处理属性。 在Python中,一切皆对象,每个类的实例或变量都有它自己的内存地址或身份。 对象就是类的实例,应用开发就是通过让对象交互来实现目的的过程。
- 他们表示所开发的应用程序内的实体
- 实体之间可以通过交互来解决现实世界的问题。
例如,Person是实体,而Car也是实体。Person可以驾驶Car,从一个地方开到另一个地方。
类可以帮助开发人员表示现实世界中的实体。
- 类可以定义对象的属性和行为。属性是数据成员,行为由成员函数表示。
- 类包含了构造函数,这些函数的作用是为对象提供初始状态。
- 类就像模板一样,非常易于重复使用。
例如,类Person可以带有属性name和age,同时提供成员函数gotoOffice(),以定义去办公室的行为。
方法在面向对象世界中的作用:
- 它们表示对象的行为。
- 方法可以对属性进行处理,从而实现所需的功能。 创建类和对象的例子
class Person(object):
def __init__(self, name, age): # constructor
self.name = name # data members/ attributes
self.age = age
def get_person(self, ): # member function
return "<Person (%s, %s)>" % (self.name, self.age)
p = Person("John", 32) # p is an object of type Person
print("Type of Object:", type(p), "Memory Address:", id(p))
上述代码输出结果如下:
Type of Object: <class '__main__.Person'> Memory Address: 4378733296
主要特点:
- 对象的行为对于外部世界来说是不可见的,或者说对象的状态信息是私密的。
- 客户端不能通过直接操作来改变对象的内部状态。 相反,客户端需要通过发送消息来请求兑现改变其内部状态。 对象可以根据请求的类型,通过特定的成员函数(例如get和set) 改变它们的内部状态,以做出相应的响应。
- 在Python中,封装(数据和方法的隐藏)的概念不是隐式的, 因为它没有提供封装所需的关键字,诸如public,private,protected (在诸如C++或Java之类语言中,都提供了这类关键字)。 但在变量和函数名前面加上前缀_(protected)或__(private),就可以达到相同效果
主要特征:
- 多态有两种类型:
- 对象根据输入参数提供方法的不同实现。
- 不同类型的对象可以使用相同的接口。
- 对于Python来说,多态是该语言的内置功能。 例如,操作符“+”可以应用于两个整数以进行假发运算,也可以应用于字符串来连接它们。 字符串、元组、列表都可以通过整数索引进行访问,这展示了Python内置类型的多态:
a = "John"
b = (1, 2, 3)
c = [1, 3, 4, 6, 8, 10]
print(a[2], b[0], c[3])
以上代码输出如下:
h 1 6
- 继承表示一个类可以继承父类的(大部分)功能。
- 继承被描述为一个重用基类中定义的功能并允许对原始软件的实现进行独立扩展的选项。
- 继承可以利用不同类的对象之间的关系建立层次结构。 与Java不同,Python支持多重继承(继承多个基类)
继承示例:
class A:
def a1(self):
print("a1")
class B(A):
def b(self):
print("b")
b = B()
b.a1()
输出:
a1
主要特征:
- 它提供了一个简单的客户端接口,客户端可以通过该接口与类的对象进行交互, 并可以调用该接口中定义的各个方法。
- 它将内部类的复杂性抽象为一个接口,这样客户端就不需要知道内部实现了。 下面例子,我们通过add()方法对类Adder的内部细节进行了抽象处理:
class Adder:
def __init__(self):
self.sum = 0
def add(self, value):
self.sum += value
acc = Adder()
for i in range(100):
acc.add(i)
print(acc.sum)
输出:
4950
- 它是一种将对象或者类组合成更复杂的数据结构或软件实现的方法。
- 在组合中,一个对象可用于调用其他模块中的成员函数,这样一来, 无需通过继承就可以实现基本功能的跨模块使用。 下面示例中,类A的对象被组合到了类B中:
class A(object):
def a1(self):
print("a1")
class B(object):
def b(self):
print("b")
A().a1()
objectB = B()
objectB.b()
输出:
b
a1
当我们深入细致地学习设计模式时,面向对象的设计原则将作为工具箱使用
开放/封闭原则规定,类或者对象及其方法对于扩展来说,应该是开放的, 但是对于修改来说,应该是封闭的。 简单地说,这意味着当你开发软件应用的时候,一定确保以通用的方式来编写类或模块, 以便每当需要扩展类或对象行为的时候不必修改类本身。 相反,类的简单扩展将有助于建立新的行为。 例如,开放/封闭原则能够在下列情形中表现的淋漓尽致:为了实现所需行为, 用户必须通过扩展抽象基类来创建类的实现,而不是通过修改抽象类。 该设计原则的优点如下:
- 现有的类不会被修改,因此退化的可能性较小。
- 它还有助于保持以前代码的向后兼容性。
控制反转原则是指,高层级的模块不应该依赖于低层级的模块, 它们都应该依赖于抽象。 细节应该依赖于抽象,而不是抽象依赖于细节。 该原则建议任何两个模块都不应该以紧密方式相互依赖。事实上, 基本模块和从属模块应当在它们之间提供一个抽象层来耦合。 这个原则还建议,类的细节应该描绘抽象。
在某些情况下,这种观念会反转,也就是实现细节本身决定了抽象,这种情况是应该避免的。
控制反转原则的优点如下:
- 削弱了模块间的紧耦合,因此消除了系统中的复杂性/刚性。
- 由于在依赖模块之间有一个明确的抽象层(由钩子或者参数提供), 因此便于通过更好的方式处理模块之间的依赖关系。
接口隔离原则规定,客户端不应该依赖于它们不需要使用的接口。 接口隔离原则的意思就是,软件开发人员应该仔细地处理接口。 例如,它提醒开发人员/架构师开发的方法要与特定功能紧密相关。 如果存在与接口无关的方法,那么依赖于该接口的类就必须实现它,实际上这是毫无必要的。 例如,一个Pizza接口不应该提供名为add_chicken()的方法。 基于Pizza结构的Veg Pizza类不应该强制实现该方法。 该设计原则优点:
- 他强制开发人员编写“瘦身型”接口,并使方法与接口紧密相关。
- 防止向接口中随意添加方法。
单一职责的含义是:类的职责单一,引起类变化的原因单一。 这个原则是说,当我们开发类时,它应该为特定的功能服务。 如果一个类实现了两个功能,那么最好将它们分开。 也就是说,功能才是改变的理由。 例如,一个类可以因为所需行为的变化而进行修改, 但是如果一个类由于两个因素(基本上是两个功能的改变)而改变, 那么该类就应该进行相应的分割。 该设计原则的优点:
- 每当一个功能发生变化时,除了特定的类需要改变外,其他类无需变动。
- 此外,如果一个类有多种功能,那么依赖它的类必定会由于多种原因 而经历多次修改,这是应该避免的。
替换原则规定,派生类必须能够完全取代基类。 这个原则很简单,当应用程序开发人员编写派生类时,该原则的含义就是他们应该扩展基类。 此外,它还建议派生类应该尽可能对基类封闭,以至于派生类本身可以替换基类, 而无需修改任何代码。
什么是设计模式呢? 根据GoF的观点,设计模式就是解决特定问题的解决方案。 设计模式本身是一种发现,而不是一种发明。 设计模式的主要特点:
- 它们是语言无关的,可以用多种语言实现。
- 它们是动态的,随时会有新的模式引入。
- 它们可以进行定制,因此对开发人员非常有用。
你必须尝试解决设计模式想要解决的问题,也许你的解决方案并不完善, 而我们所追求的完善性正是设计模式中固有的或隐含的。 当我们提到完整性时,它可以指许多因素。例如,设计、可扩展性、 重用、内存利用率等。从本质上说,设计模式就是从别人的成功而非 自己的失败中进行学习! 关于设计模式的另一个有趣的讨论是,什么时候使用他们?他是应用在 软件开发生命周期(Software Development Life Cycle,SDLC) 的分析或设计阶段吗? 有趣的是,设计模式是已知问题的解决方案。 因此设计模式在分析或设计阶段非常有用,并且如预期的那样, 在开发阶段也非常有用,因为他们与应用的编程直接相关。
- 它们可以在多个项目中重复使用。
- 问题可以在架构级别得到解决。
- 它们都经过了时间的验证和良好的证明,是开发人员和架构师的宝贵经验。
- 它们具有可靠性和依赖性。
不是每一段代码或者每一种设计都可以叫做设计模式。 例如,解决一个问题的编程构造或数据结构就不能被称为模式。 下面通过一种简单的方式来理解这些术语。
- 代码段:用某种语言编写的一段具有特定用途的代码,例如,他可以是Python 中的DB连接代码。
- 设计:用来解决某个特定问题的优秀解决方案。
- 标准:这是一种解决某类问题的方法,它非常通用,并适用于当前的情况。
- 模式:这是一个经过时间考验的、高效、可扩展的解决方案,能够解决一类已知问题。
为了有效使用设计模式,应用程序开发人员必须了解设计模式所适用的上下文。 我们可以将上下文分为以下几种主要类型:
- 参与者:它们是在设计模式中用到的类。类可以在模式中扮演不同的角色,以完成多个目标。
- 非功能需求:诸如内存优化、可用性和性能等需求都属于此类型。 由于这些因素影响整个软件解决方案,因此至关重要。
- 权衡:并非所有的设计模式都适合于应用程序开发,因此需要权衡。 这些是在应用程序中使用谁模式时所做的决策。
- 结果:如果上下文不合适,设计模式可能对代码的其他部分产生负面影响。 开发人员应该了解设计模式的结果和用途。
就像Lisp一样,Python也是一种动态语言。 Python动态特性如下:
- 类型或类是运行时对象。
- 变量可以根据赋值来确定类型,并且类型可以在运行时改变。例如, a=5和a="john",变量a在运行时被赋值,而且其类型也发生了变化。
- 动态语言在类限制方面具有更大的灵活性。 例如,在Python中,多态性是该语言所固有的,并没有诸如private 和protected之类的关键字,因为默认情况下一切都是公共的。
- 可以使用动态语言轻松实现设计模式的用例。
GoF在他的设计模式中讲到了23种设计模式,并将它们分为3大类。
- 创建型模式
- 结构型模式
- 行为型模式
模式的分类主要基于对象的创建方式、软件应用程序中类和对象的构造方式, 同时还涉及对象之间的交互方式。
创建型模式的性质:
- 它们的运行机制基于对象的创建方式。
- 它们将对象创建的细节隔离开来。
- 代码与所创建的对象的类型无关。
单例模式是创建型模式的一个例子。
结构型模式的性质:
- 它们致力于设计出能够通过组合获得更强大功能的对象和类的结构。
- 重点是简化结构并识别类和对象之间的关系。
- 它们主要关注类的继承和组合。
适配器模式是结构型模式的一个例子。
行为型模式的性质:
- 它们关注对象之间的交互以及对象的响应性。
- 对象应该能够交互,同时仍然保持松散耦合。
观察者模式是行为模式的一个例子。