YangYuchen
软件体系结构期末复习(三)

软件体系结构期末复习(三)

此文章仅用作期末复习

一、Event-driven Architecture

Event-driven Architecture是一种软件设计模式,其中系统组件通过事件和消息相互通信,以响应事件和完成任务。它是一种异步编程模型,用于解决传统同步编程模型中出现的并发问题和性能瓶颈。

其作用和意义在于:

  1. 实现系统组件的松耦合:组件之间的通信是通过事件和消息进行,因此可以实现组件的松耦合,从而更容易实现复杂系统的开发和维护。

  2. 提高系统的可扩展性:事件驱动的系统具有高可扩展性,因为不同的组件可以独立地运行,而不会对其他组件产生影响。

  3. 实现高并发、高性能的系统:事件驱动的系统可以同时处理多个事件和任务,从而实现高并发、高性能的系统。

下面是一个简单的Python代码示例,展示了如何使用事件驱动的方式实现一个简单的聊天室:

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
import socket
import select

class ChatRoom:
def __init__(self):
self.clients = []
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server.bind(('localhost', 8000))
self.server.listen(5)

def run(self):
self.clients.append(self.server)
while True:
readable, _, _ = select.select(self.clients, [], [])
for sock in readable:
if sock == self.server:
client, _ = sock.accept()
self.clients.append(client)
print('New client joined')
else:
data = sock.recv(1024)
if data:
print(f'Received message: {data.decode()}')
for client in self.clients:
if client != self.server and client != sock:
client.send(data)
else:
sock.close()
self.clients.remove(sock)
print('Client left')

if __name__ == '__main__':
room = ChatRoom()
room.run()

在这个例子中,ChatRoom类表示了一个聊天室。在聊天室中,每个客户端可以发送消息,其他客户端会收到这些消息。聊天室使用了select模块来实现事件驱动的方式。每当有新客户端加入或有客户端发送消息时,聊天室就会响应相应事件,并执行对应的处理逻辑。

二、Redis pub/sub(发布/订阅)

Redis pub/sub(发布/订阅)是一种消息通信模式,用于解耦消息的发送者和接收者。它包括两个主要组件:发布者和订阅者。发布者可以将消息发布到一个或多个特定频道,而订阅者则可以订阅一个或多个频道以接收该频道的消息。

Redis pub/sub的主要作用和意义是在分布式系统中传递消息。它可以帮助不同的服务之间进行通信,使得系统更加灵活和可扩展,同时也可以减少服务之间的依赖,降低耦合度。

在Redis中,channel是指一个消息的频道,每个频道可以有多个订阅者,同时也可以有一个或多个发布者。当一个消息被发布到某个频道时,所有订阅该频道的订阅者都将会收到该消息。

下面是一个使用Python实现Redis pub/sub的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import redis

# 创建一个Redis连接
r = redis.StrictRedis(host='localhost', port=6379, db=0)

# 创建一个发布者,将消息发送到'news'频道
p = r.pubsub()
p.publish('news', 'Hello, World!')

# 创建一个订阅者,订阅'news'频道
s = r.pubsub()
s.subscribe('news')

# 读取订阅到的消息
for message in s.listen():
print(message)

在上面的例子中,我们首先创建了一个Redis连接,并使用发布者发布了一条消息到’news’频道。接着,我们创建了一个订阅者并订阅了’news’频道。最后,我们使用listen()函数读取订阅到的消息,并打印出来。

三、Message Processor

Message Processor是指一个程序或服务,用于处理消息或事件,通常是从消息队列或事件总线中获取消息或事件,然后对其进行转换、处理或分发。其作用和意义是帮助系统进行异步处理,提高系统的可扩展性和可靠性。

举个例子,假设有一个在线购物网站,用户下单后需要发送一封确认邮件和将订单信息保存到数据库中。这些操作需要时间,如果在用户下单时即时处理,会让用户等待很久,影响用户体验。因此,可以将这些操作作为消息发送到消息队列中,然后由Message Processor进行处理,这样就可以让用户快速完成下单操作,而不需要等待。

以下是用Python实现一个简单的Message Processor的代码示例:

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
import time
import random
import threading
from queue import Queue

class MessageProcessor:
def __init__(self):
self.queue = Queue()
self.is_running = True

def start(self):
while self.is_running:
if not self.queue.empty():
message = self.queue.get()
self.process_message(message)
time.sleep(0.1)

def stop(self):
self.is_running = False

def put_message(self, message):
self.queue.put(message)

def process_message(self, message):
print(f"processing message: {message}")
time.sleep(random.randint(1, 5))
print(f"message processed: {message}")

if __name__ == "__main__":
processor = MessageProcessor()

def add_message():
while True:
message = random.randint(1, 10)
processor.put_message(message)
time.sleep(1)

threading.Thread(target=add_message, daemon=True).start()
processor.start()

该示例中,Message Processor使用队列来存储消息,使用一个线程来处理消息,可以不断地向队列中添加消息,而Message Processor会不断地获取并处理消息。其中,process_message方法可以根据实际需求实现具体的消息处理逻辑。

四、依赖反转原则(DIP)

依赖反转原则(DIP)是面向对象设计中的一项原则,它强调高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。这样做的好处是可以提高系统的灵活性和可扩展性。

DIP的意义在于,通过将依赖关系的方向反转,可以使得代码更加灵活和易于修改。具体而言,它使得系统更容易实现替换和扩展具体实现,从而增强了系统的可维护性和可测试性。

以下是一个使用DIP原则的Python代码示例:

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
class DataSource():
def __init__(self, data):
self._data = data

def read_data(self):
pass

class FileDataSource(DataSource):
def read_data(self):
with open(self._data) as f:
data = f.read()
return data

class DatabaseDataSource(DataSource):
def read_data(self):
connection = pymysql.connect(host='localhost',
user='user',
password='password',
db='database')
with connection.cursor() as cursor:
sql = "SELECT * FROM data"
cursor.execute(sql)
data = cursor.fetchone()
return data

def process_data(data_source):
data = data_source.read_data()
# process data
pass

file_data_source = FileDataSource('data.txt')
process_data(file_data_source)

database_data_source = DatabaseDataSource()
process_data(database_data_source)

在这个示例中,我们定义了一个DataSource类作为抽象基类,其中包含了read_data方法,但并不实现该方法。我们再定义了两个子类FileDataSourceDatabaseDataSource,它们分别从文件和数据库中读取数据,并实现了read_data方法。process_data函数接受一个DataSource对象,并调用其read_data方法来读取数据并进行后续处理。

这个示例中,我们使用了DIP原则,通过定义抽象基类DataSource,将依赖关系从高层模块转移到了抽象基类中。这样做的好处是,如果我们需要以后增加其他数据源,只需要定义一个新的子类,并实现抽象基类中的read_data方法即可。这个过程是非常容易的,因为我们定义了一个抽象基类来规范子类的行为,并将依赖关系的方向反转,使得高层模块只依赖于抽象,而不依赖于具体实现。

五、Thin Web Controller Principle

Thin Web Controller Principle是一种设计原则,指的是将web应用程序中的控制器逻辑保持尽可能的简洁和薄。这种原则的作用是避免控制器逻辑过于复杂和混乱,同时使得控制器逻辑的维护更加容易和清晰。

Thin Web Controller Principle通常包括以下几个方面的内容:

  1. 控制器应该只负责数据的验证和处理,不应该直接参与业务逻辑的处理。

  2. 控制器应该尽可能的保持短小精悍,不要过多的包含业务逻辑。

  3. 控制器应该尽可能的避免处理一些复杂的业务场景,应该将这些业务场景转交给其他的模块来处理。

Python代码示例:

在一个Flask框架的项目中,实现一个简单的登录接口,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')

if not username or not password:
return jsonify({'code': 400, 'msg': '用户名或密码不能为空'})

# 验证用户名和密码是否正确
if username == 'admin' and password == '123456':
return jsonify({'code': 200, 'msg': '登录成功'})
else:
return jsonify({'code': 400, 'msg': '用户名或密码错误'})

if __name__ == '__main__':
app.run(debug=True)

在上面的代码中,我们只处理了用户的输入和验证,没有涉及到具体的业务逻辑。这样可以避免控制器逻辑的复杂性,使得代码更加简洁易懂。具体的业务逻辑可以放在其他的模块中处理,这样可以更好地实现代码的模块化和可维护性。

六、Dependency Injection (DI)依赖注入

Dependency Injection(DI)是一种设计模式,它的主要目的是通过将对象的依赖关系交给外部管理,从而实现更松散的耦合和更高的可重用性。简而言之,DI就是让对象对它所需要的依赖项进行解耦。

在DI中,对象不再自己创建它所需要的依赖项,而是由外部容器或框架来注入它们。这意味着对象只需要专注于它自己的责任,而不用担心如何满足它的依赖项。

下面是一个简单的Python示例,演示了如何使用DI实现依赖项注入:

1
2
3
4
5
6
7
8
9
10
11
class EmailService:
def send_email(self, message):
print(f"Sending email: {message}")

class UserService:
def __init__(self, email_service):
self.email_service = email_service

def register(self, name, email):
print(f"Registering user: {name} ({email})")
self.email_service.send_email(f"Welcome {name}!")

在上面的示例中,我们定义了两个类,一个是EmailService,用于发送电子邮件,另一个是UserService,用于注册用户。UserService类有一个依赖项email_service,它的类型是EmailService。在__init__构造函数中,我们将EmailService实例作为参数传入,并将它保存到self.email_service中。

现在,我们需要创建UserService实例并调用它的register方法。当我们创建UserService实例时,我们需要将一个EmailService实例注入到它的构造函数中。下面是一个示例:

1
2
3
email_service = EmailService()
user_service = UserService(email_service)
user_service.register("John", "john@example.com")

在上面的示例中,我们首先创建一个EmailService实例,然后将它注入到UserService的构造函数中,最后我们调用UserServiceregister方法,该方法会使用EmailService实例来发送欢迎邮件给新用户。这就是依赖注入的基本流程。

当我们使用DI时,我们需要一个容器或框架来管理依赖项的注入。这些容器或框架会自动创建和管理对象的实例,并将它们的依赖项注入到它们的构造函数中。下面是一个简单的Python示例,演示了如何使用injector模块来进行DI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from injector import Injector, inject

class EmailService:
def send_email(self, message):
print(f"Sending email: {message}")

class UserService:
@inject
def __init__(self, email_service: EmailService):
self.email_service = email_service

def register(self, name, email):
print(f"Registering user: {name} ({email})")
self.email_service.send_email(f"Welcome {name}!")

injector = Injector()
user_service = injector.get(UserService)
user_service.register("John", "john@example.com")

在上面的示例中,我们使用了injector模块来创建一个Injector实例,并使用它来获取一个UserService实例。UserService的构造函数中声明了一个名为email_service的依赖项,并使用@inject装饰器将它标记为需要注入的依赖项。当我们调用injector.get(UserService)时,injector会自动创建一个EmailService实例,并将它注入到UserService实例的构造函数中。

总之,使用DI可以让我们的代码更加模块化和可重用,使得不同模块之间的耦合更加松散。在Python中,我们可以使用injector模块等工具来实现依赖项注入。

本文作者:YangYuchen
本文链接:https://www.littlewhite.site/软件体系结构复习-三/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可