工作一段时间了,工作内容趋向于算法模型的复现,就是复现论文算法然后结合业务逻辑开发软件。但是在设计和开发软件时,发现对于OOP理念和软件的设计原则等在实战中还是非常缺乏。于是开始补习,基础软件开发技术。
书籍:《深入浅出WPF》
作者:刘铁猛
序:WPF(Windows Presentation Foundation)编写程序表示层的技术与工具。
程序架构: 表示层(View)<-->业务逻辑层(ViewModel)<-->数据层(Model)。也就是一般常说的MVVM。
数据模型:现实世界中事物和逻辑的抽象。
业务逻辑:数据模型之间的关系与交互。
用户界面:由控件构成的、与用户进行交互的界面,用于把数据展示给用户并响应用户的输入。
好,序结束了,本书可以暂告一段落了。先插入OOP,再来WPF的具体开发。
OOP(Object-Oriented Programming)实践:(以下代码示例由ChatGPT给出)
目录
1. 对象组合
1.1 一对一对象组合:两种典型的对象组合方式
1.1.1 方式一: 完全包容
1.1.2 方式二:独立方式
1.2 一对多
编辑
1.2.1 方式一:完全包含
1.2.2 方式二:独立方式
1.3 延时动态对象组合
1.4 对象组合的特殊形式——自引用类
示例:链表中的自引用
自引用类的应用场景:
自引用类的特点:
1. 对象组合
假设我们有一个 Engine
类和一个 Wheels
类,通过组合它们,我们可以创建一个 Car
类,而不必通过继承的方式来实现。
class Engine:
def start(self):
print("Engine started")
class Wheels:
def roll(self):
print("Wheels rolling")
class Car:
def __init__(self, engine, wheels):
self.engine = engine
self.wheels = wheels
def drive(self):
self.engine.start()
self.wheels.roll()
# 使用对象组合
engine = Engine()
wheels = Wheels()
car = Car(engine, wheels)
car.drive()
1.1 一对一对象组合:两种典型的对象组合方式
1.1.1 方式一: 完全包容
在面向对象编程中,如果对象 A 完全包容对象 B,并且容器对象 A 管理被包容对象 B 的生命周期,这种关系通常被称为 组合(Composition)。
下面是一个使用 C# 语言的例子,
-
Engine 类 定义了引擎的启动和停止行为。
-
Car 类 内部包含一个
Engine
对象,并在Car
对象的构造函数中创建该对象。 -
Car 类 通过
Drive
和Stop
方法来操作其内部的Engine
对象。 -
当
Car
对象被销毁时,Engine
对象也随之被销毁。这种关系说明Car
对象完全控制了Engine
对象的生命周期。
在这个例子中,Car
对象是容器对象,它负责创建和销毁 Engine
对象,展示了组合关系中的生命周期管理。
using System;
class Engine
{
public void Start()
{
Console.WriteLine("Engine started");
}
public void Stop()
{
Console.WriteLine("Engine stopped");
}
}
class Car
{
private Engine _engine;
public Car()
{
_engine = new Engine(); // Car对象创建时,同时创建Engine对象
}
public void Drive()
{
_engine.Start();
Console.WriteLine("Car is driving...");
}
public void Stop()
{
_engine.Stop();
Console.WriteLine("Car has stopped.");
}
~Car()
{
// 在Car对象销毁时,同时销毁Engine对象
Console.WriteLine("Car is being destroyed, so is the engine.");
_engine = null;
}
}
class Program
{
static void Main()
{
Car car = new Car();
car.Drive();
car.Stop();
// Car对象出作用域时,垃圾回收器将最终销毁Car对象和它包含的Engine对象
}
}
1.1.2 方式二:独立方式
在面向对象编程中,如果对象 B 是独立的,并且对象 A 只是引用一个现成的 B 对象,而不负责管理 B 对象的生命周期,这种关系通常被称为 聚合(Aggregation)。在聚合关系中,被引用的对象可以被多个其他对象共享,它的生命周期独立于引用它的对象。
-
Engine 类 定义了引擎的启动和停止行为,与前面的例子相同。
-
Car 类 在其构造函数中接受一个
Engine
对象的引用,但并不创建或销毁该对象。 -
在
Main
方法中,sharedEngine
是一个独立的Engine
对象,它的生命周期独立于任何Car
对象。 -
Car1 和 Car2 都引用同一个
sharedEngine
对象,这表明它们之间存在聚合关系。
在这个例子中,Car
对象和 Engine
对象之间的关系是聚合关系,因为 Car
对象只引用现成的 Engine
对象,而不管理它的生命周期。
using System;
class Engine
{
public void Start()
{
Console.WriteLine("Engine started");
}
public void Stop()
{
Console.WriteLine("Engine stopped");
}
}
class Car
{
private Engine _engine;
// 构造函数接受一个现成的 Engine 对象
public Car(Engine engine)
{
_engine = engine;
}
public void Drive()
{
_engine.Start();
Console.WriteLine("Car is driving...");
}
public void Stop()
{
_engine.Stop();
Console.WriteLine("Car has stopped.");
}
}
class Program
{
static void Main()
{
Engine sharedEngine = new Engine(); // 创建一个独立的 Engine 对象
Car car1 = new Car(sharedEngine); // 引用现成的 Engine 对象
Car car2 = new Car(sharedEngine); // 另一个 Car 对象也引用同一个 Engine 对象
car1.Drive();
car1.Stop();
car2.Drive();
car2.Stop();
// sharedEngine 对象的生命周期独立于 car1 和 car2
}
}
1.2 一对多
1.2.1 方式一:完全包含
一对多完全包容的特点:
-
生命周期管理:容器对象(如
Team
)负责管理所有子对象(如Player
)的生命周期。一旦容器对象销毁,所有子对象也会被销毁。 -
封装性:所有子对象都被容器对象内部管理,外部不需要直接处理这些子对象。
-
统一管理:通过使用集合类型(如
List<Player>
),可以方便地对多个子对象进行统一管理和操作。
一对多组合关系的例子,其中一个 Team
对象完全包容并管理多个 Player
对象:
using System;
using System.Collections.Generic;
class Player
{
public string Name { get; }
public Player(string name)
{
Name = name;
}
public void Play()
{
Console.WriteLine($"{Name} is playing.");
}
}
class Team
{
private List<Player> _players;
public Team()
{
_players = new List<Player>(); // 初始化列表以管理多个Player对象
}
public void AddPlayer(string playerName)
{
Player player = new Player(playerName); // 创建并添加新Player对象
_players.Add(player);
}
public void PlayGame()
{
Console.WriteLine("The team is playing the game:");
foreach (var player in _players)
{
player.Play();
}
}
~Team()
{
// 在Team对象销毁时,Player对象会随之销毁
Console.WriteLine("Team is being destroyed, so are the players.");
_players.Clear();
}
}
class Program
{
static void Main()
{
Team team = new Team();
team.AddPlayer("Alice");
team.AddPlayer("Bob");
team.AddPlayer("Charlie");
team.PlayGame();
// 当 team 对象出作用域并被垃圾回收时,Player 对象也会被销毁
}
}
1.2.2 方式二:独立方式
一对多独立关系的特点:
-
独立的生命周期:
Student
对象的生命周期独立于School
对象,即使School
对象被销毁,Student
对象依然可以存在并被其他对象引用。 -
共享性:多个
School
对象可以共享同一个Student
对象,或者Student
对象可以被其他地方使用,而不仅仅是被某个特定的School
对象引用。 -
低耦合性:
School
对象和Student
对象之间的关系是低耦合的,School
仅仅是引用和使用Student
,而不负责其生命周期管理。
一对多聚合关系的例子,其中一个 School
对象引用多个独立的 Student
对象:
using System;
using System.Collections.Generic;
class Student
{
public string Name { get; }
public Student(string name)
{
Name = name;
}
public void Study()
{
Console.WriteLine($"{Name} is studying.");
}
}
class School
{
private List<Student> _students;
public School()
{
_students = new List<Student>(); // 初始化列表用于引用多个Student对象
}
public void AddStudent(Student student)
{
_students.Add(student); // 将独立的Student对象添加到School中
}
public void StartClasses()
{
Console.WriteLine("School is starting classes:");
foreach (var student in _students)
{
student.Study();
}
}
}
class Program
{
static void Main()
{
// 创建独立的Student对象
Student student1 = new Student("Alice");
Student student2 = new Student("Bob");
Student student3 = new Student("Charlie");
// 创建School对象
School school = new School();
// 将Student对象添加到School中
school.AddStudent(student1);
school.AddStudent(student2);
school.AddStudent(student3);
// 开始上课,所有学生都参与学习
school.StartClasses();
// Student对象的生命周期独立于School对象
// 即使School对象销毁,Student对象仍然存在并可以被其他对象引用
}
}
1.3 延时动态对象组合
延时动态对象组合是一种设计模式,其中对象的组合并不是在对象创建时立即完成,而是在需要时动态地完成。这种模式通常用于延迟初始化,资源管理,或者当组合的对象在创建时不一定存在时。通过延时组合,可以提高系统的灵活性和性能,特别是在对象的创建和初始化成本较高的情况下。
延时动态对象组合的优势:
-
性能优化:对象只在需要时才会被创建,节省了系统资源,特别是当对象创建代价高或对象不一定总是需要时。
-
提高灵活性:延迟初始化允许系统在不同的时间点根据需要动态组合对象,从而可以灵活地响应运行时的需求。
-
资源管理:避免在程序启动时创建不必要的对象,减少内存占用和初始化时间。
一个 Document
对象,其中包含一个 Printer
对象。Printer
对象只有在需要时才会被创建和初始化:
using System;
class Printer
{
public Printer()
{
Console.WriteLine("Printer is initialized.");
}
public void Print(string content)
{
Console.WriteLine($"Printing: {content}");
}
}
class Document
{
private Printer _printer;
// 延时初始化 Printer 对象
public Printer Printer
{
get
{
if (_printer == null)
{
_printer = new Printer(); // 在需要时才初始化 Printer 对象
}
return _printer;
}
}
public void PrintDocument(string content)
{
Console.WriteLine("Preparing to print document...");
Printer.Print(content); // 使用延时初始化的 Printer 对象进行打印
}
}
class Program
{
static void Main()
{
Document doc = new Document();
// Document 对象创建时,Printer 对象尚未初始化
Console.WriteLine("Document created.");
// 只有在需要打印时,Printer 对象才会被初始化
doc.PrintDocument("Hello, World!");
// 如果不需要打印,Printer 对象将不会被创建
}
}
Document created.
Preparing to print document...
Printer is initialized.
Printing: Hello, World!
解释:
-
Printer 类:定义了一个
Printer
对象,它有一个Print
方法用于打印内容。在创建Printer
对象时会输出初始化信息。 -
Document 类:
-
包含一个私有的
Printer
字段_printer
,初始值为null
。 -
通过一个
Printer
属性进行延时初始化。只有在第一次访问Printer
属性时,_printer
才会被创建和初始化。 -
PrintDocument
方法中使用了Printer
属性。当需要打印文档时,Printer
对象才会被动态创建和使用。
-
-
Main 方法:
-
创建了一个
Document
对象时,并没有立即创建Printer
对象。 -
只有在调用
PrintDocument
方法时,Printer
对象才会被初始化和使用。 -
上述两种非延时动态对象组合的方式,在Main中将所有独立的对象都进行了初始化,或者通过对象A的初始化是,对象B也在对象A的类内部进行了初始化。
-
1.4 对象组合的特殊形式——自引用类
自引用类是一种特殊形式的对象组合,其中一个类包含一个对同类对象的引用。自引用类常用于构建递归结构或树形结构,如链表、树、图等数据结构。通过自引用,一个对象可以引用另一个同类型的对象,形成层次化或递归的结构。
示例:链表中的自引用
链表是一个常见的自引用结构。每个节点(Node
)都包含一个数据部分以及一个指向下一个节点的引用。这种结构允许创建一个动态大小的序列。
使用 C# 实现的单向链表,其中 Node
类是一个自引用类:
using System;
class Node
{
public int Data { get; set; }
public Node Next { get; set; } // 自引用,指向同类型的下一个节点
public Node(int data)
{
Data = data;
Next = null;
}
}
class LinkedList
{
private Node head;
public LinkedList()
{
head = null;
}
// 添加节点到链表末尾
public void Add(int data)
{
Node newNode = new Node(data);
if (head == null)
{
head = newNode; // 链表为空时,设置新节点为头节点
}
else
{
Node current = head;
while (current.Next != null)
{
current = current.Next; // 找到链表的最后一个节点
}
current.Next = newNode; // 将新节点连接到链表末尾
}
}
// 打印链表中的所有节点
public void PrintAllNodes()
{
Node current = head;
while (current != null)
{
Console.WriteLine(current.Data);
current = current.Next;
}
}
}
class Program
{
static void Main()
{
LinkedList list = new LinkedList();
list.Add(1);
list.Add(2);
list.Add(3);
list.PrintAllNodes(); // 输出链表中的所有节点值
}
}
自引用类的应用场景:
-
链表:如示例中的单向链表、双向链表、循环链表等。
-
树结构:如二叉树、B 树、红黑树等,每个节点包含对子节点的引用。
-
图结构:节点间包含相互引用,形成复杂的网络关系。
-
递归结构:支持递归定义和操作的复杂数据结构。
自引用类的特点:
-
递归结构:自引用类可以自然地表达递归结构,适用于分层或递归数据的表示和操作。
-
动态结构:通过自引用,可以构建动态大小和深度的数据结构,如链表和树。
-
灵活性:自引用允许对象在运行时动态地链接和操作其他同类对象,提供灵活的数据管理能力。
自引用类的设计模式在构建动态、递归或分层结构时非常有用,广泛应用于各种数据结构和算法中。