[TOC]
设计模式学习(二)——《大话设计模式》
1.属性和修饰符
属性(Attributes)
属性是类中定义的变量,用于存储对象的状态信息。它们代表了对象的特征,比如一个Person类可能有name和age属性。在设计模式中,如何定义和使用属性对于实现特定模式的效果至关重要。
私有属性:一般用于类内部,不希望外部直接访问。在Java中使用private修饰符,在Python中可以通过前缀__来表示(虽然这只是一种约定,并非真正意义上的私有)。 公有属性:可以被外部访问和修改。通常公开访问的属性应该谨慎使用,以避免破坏对象的封装性。 受保护属性:这类属性通常只允许在类本身以及其子类中访问,使用protected修饰符。
代码示例:
class Person:
def __init__(self, name, age):
self.name = name # 公有属性
self.__age = age # 私有属性,通过双下划线前缀表示
def display_info(self):
print(f"Name: {self.name}, Age: {self.__age}")
# 使用类
person = Person("Alice", 30)
print(person.name) # 正常访问
# print(person.__age) # 会引发错误,因为__age是私有属性
person.display_info() # 通过类的公有方法访问私有属性
修饰符(Modifiers)
修饰符定义了对类、方法或属性的访问控制级别。它们在不同的编程语言中可能有所不同,但常见的有:
public(公有):成员对所有类可见。 private(私有):成员只对定义它们的类可见。 protected(受保护):成员对定义它们的类及其子类可见。 default(默认,仅Java):如果没有指定修饰符,则使用默认访问级别,成员对同一包内的类可见。 final(最终,主要用于Java):表示变量值一旦被赋值后不能改变,方法不能被重写,类不能被继承。 在设计模式中的应用 单例模式(Singleton):通常将构造函数设置为私有(private),以确保只能从类内部创建实例。 工厂模式(Factory Method):可以使用受保护(protected)或私有(private)修饰符来限制对工厂方法的访问,从而控制对象的创建。 装饰器模式(Decorator):通过扩展类的功能而不修改其原始代码,通常需要对原始类的属性和方法进行访问,这可能涉及到对属性和方法访问级别的调整。 策略模式(Strategy):可以将策略接口作为公有(public)属性或方法暴露出来,以便在运行时切换策略。
代码示例
#@property装饰器允许你将一个方法转化为只读属性,并可以结合setter装饰器来控制属性的修改。
class Person:
def __init__(self, name, age):
self._name = name # 使用单下划线定义受保护属性
self.__age = age # 私有属性
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("Age cannot be negative")
self.__age = value
@property
def name(self):
return self._name
# 使用类
person = Person("Bob", 25)
print(person.name) # 访问公有属性
print(person.age) # 通过@property装饰的方法访问私有属性
person.age = 26 # 修改私有属性值
# person.age = -1 # 尝试设置不合法的年龄,会引发ValueError
2.封装
封装是面向对象编程(OOP)的三大基本特征之一,另外两个是继承和多态。封装的核心思想是将对象的数据(属性)和操作这些数据的方法绑定在一起,形成一个紧密的单元,并对外隐藏对象的具体实现细节。这样做的目的是提高代码的安全性、可维护性和复用性。
封装的主要目的:
- 隐藏实现细节:用户只需要知道对象提供了哪些方法来操作数据,而不需要知道这些方法是如何实现的。
- 简化接口:通过将复杂的实现细节隐藏起来,只暴露简单的接口给外部使用,使得对象更加易于理解和使用。
- 增强安全性:限制对某些内部数据的直接访问,只能通过对象提供的方法来修改这些数据,可以避免外部的非法访问和修改,保护对象的状态安全。
封装的优势:
- 良好的封住能够减少耦合;
- 类内部的实现可以自由的修改;
- 类具有清晰的对外接口;
实现封装:
在面向对象语言中,封装通常通过使用访问修饰符(如private、protected、public)来实现。通过将类的某些属性或方法声明为私有(private),就可以防止外部直接访问这些成员,只能通过公有(public)方法来间接访问或修改这些私有成员。
代码示例:
class Account:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # 私有属性
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"Added {amount} to the balance")
else:
print("Deposit amount must be positive")
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"Withdrew {amount} from the balance")
else:
print("Invalid withdrawal amount")
def get_balance(self):
return self.__balance
# 使用
acc = Account("John", 100)
acc.deposit(50)
acc.withdraw(75)
print(acc.get_balance()) # 通过公有方法访问私有属性
# 直接访问__balance会失败
# print(acc.__balance) # AttributeError
// 封装
class Person {
private:
// 私有属性
std::string name;
public:
// 公共方法来访问私有属性
void setName(std::string n) {
name = n;
}
std::string getName() {
return name;
}
};
public class Person {
private String name; // 私有属性
// 公共方法来访问私有属性
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
3.继承
继承是面向对象编程(OOP)的另一个核心概念,它允许我们定义一个类(子类或派生类)来继承另一个类(基类或父类)的属性和方法。继承机制使得子类可以重用父类的代码,这不仅可以减少重复代码,还可以实现代码的组织和模块化。
继承的特点:
- 子类拥有父类非private的属性和功能;
- 子类具有自己的属性和功能,即子类可以扩展父类没有的属性和功能;
- 子类还可以以自己的方式实现父类的功能(方法重写);
- 继承使的所有子类公共的部分都放在了父类,使的代码得到了共享,避免了重复,继承可使得修改或者扩展而来的实现都较为容易。
- 继承的缺点
- 父类改变,则子类也会随之改变
- 继承破坏包装,父类实现细节会暴露给子类
- 继承是一种类与类之间的强耦合的关系
继承的主要目的:
- 代码重用:通过继承,子类可以使用父类中定义的方法和属性,无需重新编写相同的代码。
- 实现多态:子类可以根据需要覆盖或扩展父类的行为,提供新的实现,这是多态性的基础。
- 建立类之间的关系:继承可以表达不同类之间的层次关系,为设计更复杂的系统提供了一种自然的方式。
继承的类型:
- 单继承:一个子类只继承自一个父类。
- 多重继承:一个子类可以同时继承多个父类。在某些语言中(如Python),这是支持的,但在其他语言中(如Java),则通过接口实现类似功能。
代码示例
# 定义一个基类(父类)
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement abstract method")
# 定义一个子类,继承自Animal
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
# 定义另一个子类,也继承自Animal
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
# 使用子类
dog = Dog("Buddy")
print(dog.speak()) # 输出: Buddy says Woof!
cat = Cat("Whiskers")
print(cat.speak()) # 输出: Whiskers says Meow!
// 基类
class Animal {
public:
void eat() {
std::cout << "I can eat!" << std::endl;
}
};
// 派生类
class Dog : public Animal {
public:
void bark() {
std::cout << "I can bark! Woof woof!" << std::endl;
}
};
// 基类
class Animal {
void eat() {
System.out.println("I can eat!");
}
}
// 派生类
class Dog extends Animal {
void bark() {
System.out.println("I can bark! Woof woof!");
}
}
4.多态
多态表示不同的对象可以执行相同的动作,但是要通过它们自己的代码实现;多态性允许我们以统一的接口操作不同类型的对象,而具体的行为则取决于对象的实际类型。这种机制使得代码更加灵活和可扩展。
第一:子类以父类的身份出现;
第二:子类在工作时以自己的方式来实现;
第三:子类以父类的身份出现时,子类特有的方法和属性不可使用;
多态的工作原理:
当方法被调用时,无论对象是否被转换为其父类,都只有位于对象继承链最末端的方法实现会被调用。
- 接口的统一:多态性要求不同的对象能响应同一消息(或方法调用),即它们有共同的接口。
- 实现的差异:虽然接口统一,但不同对象对同一消息可以有不同的响应方式,即它们的实现可以不同。
多态的优点:
- 提高代码的可复用性:可以通过抽象类或接口编写通用的代码,这些代码可以与任何具体实现协作。
- 提高代码的扩展性:可以引入新的对象类型,而无需修改使用旧对象类型的代码。
- 提高代码的可维护性:多态性可以帮助隐藏具体实现的细节,使得代码更易于理解和维护。
代码示例:
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
def animal_sound(animal):
print(animal.speak())
# 创建Dog和Cat的实例
dog = Dog()
cat = Cat()
# 调用函数,传入不同的对象
animal_sound(dog) # 输出: Woof!
animal_sound(cat) # 输出: Meow!
'''函数animal_sound接受一个Animal类型的参数。由于Dog和Cat都是Animal的子类,它们都可以作为参数传递给这个函数。函数内部调用了animal.speak(),具体调用哪个版本的speak方法取决于传入对象的实际类型。这就是多态性的体现'''
// 基类
class Shape {
public:
virtual void draw() = 0; // 纯虚函数,提供接口框架
};
// 派生类1
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing Circle" << std::endl;
}
};
// 派生类2
class Square : public Shape {
public:
void draw() override {
std::cout << "Drawing Square" << std::endl;
}
};
void drawShape(Shape* shape) {
shape->draw();
}
// 基类
abstract class Shape {
abstract void draw(); // 抽象方法
}
// 派生类1
class Circle extends Shape {
void draw() {
System.out.println("Drawing Circle");
}
}
// 派生类2
class Square extends Shape {
void draw() {
System.out.println("Drawing Square");
}
}
public class TestPolymorphism {
public static void drawShape(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Shape circle = new Circle();
Shape square = new Square();
drawShape(circle);
drawShape(square);
}
}