【软件设计】设计模式 MVC

本篇文章总结的是书籍:

Design Patterns--Elements of Reusable Object-Oriented Software

设计模式_可复用面向对象软件的基础

MVC是什么?

MVC的组成:MVC模式包括模型(Model)、视图(View)和控制器(Controller)三部分。

• Model:负责应用程序的数据和业务逻辑。

• View:负责用户界面的显示。

• Controller:处理用户输入并更新Model和View。

  1. MVC的优点:

• 分离关注点:将数据、界面和输入逻辑分开,使得代码更易于管理和维护。

• 模块化:通过模块化的设计,MVC允许组件重用和独立开发。

• 灵活性:不同的视图可以共享同一个模型,实现多种用户界面。

看完是不是干巴巴的,似懂非懂,所有知识都要结合例子来理解:

具体例子:图书管理系统

假设我们要开发一个图书管理系统,通过这个系统,用户可以添加、删除和查看书籍信息。我们将使用MVC模式来实现这个系统。

  1. 模型(Model)

模型负责处理数据和业务逻辑。在这个例子中,模型包括图书的信息(如书名、作者、出版日期等)。

show me the code!

class Book:

    def __init__(self, title, author, publication_date):

        self.title = title

        self.author = author

        self.publication_date = publication_date

class LibraryModel:

    def __init__(self):

        self.books = []

        self.observers = []

    def add_book(self, book):

        self.books.append(book)

        self.notify_observers()

    def remove_book(self, book):

        self.books.remove(book)

        self.notify_observers()

    def get_books(self):

        return self.books

    def add_observer(self, observer):

        self.observers.append(observer)

    def remove_observer(self, observer):

        self.observers.remove(observer)

    def notify_observers(self):

        for observer in self.observers:

            observer.update()

  1. 视图(View)

视图负责展示数据。在这个例子中,我们有不同的视图来展示图书信息,如列表视图和详细视图。

class BookListView:
    def display_books(self, books):
        for book in books:
            print(f"Title: {book.title}, Author: {book.author}, Publication Date: {book.publication_date}")

class BookDetailView:
    def display_book(self, book):
        print(f"Title: {book.title}")
        print(f"Author: {book.author}")
        print(f"Publication Date: {book.publication_date}")
  1. 控制器(Controller)

控制器处理用户输入并更新模型。在这个例子中,控制器负责处理添加和删除书籍的操作。

class LibraryController:
    def __init__(self, model, list_view, detail_view):
        self.model = model
        self.list_view = list_view
        self.detail_view = detail_view
        self.model.add_observer(self)

    def add_book(self, title, author, publication_date):
        book = Book(title, author, publication_date)
        self.model.add_book(book)

    def remove_book(self, title):
        book = next((b for b in self.model.get_books() if b.title == title), None)
        if book:
            self.model.remove_book(book)

    def update(self):
        self.list_view.display_books(self.model.get_books())

整合MVC

现在我们将模型、视图和控制器整合起来,实现完整的图书管理系统。

if __name__ == "__main__":
    # 创建模型、视图和控制器
    model = LibraryModel()
    list_view = BookListView()
    detail_view = BookDetailView()
    controller = LibraryController(model, list_view, detail_view)

    # 添加书籍
    controller.add_book("The Great Gatsby", "F. Scott Fitzgerald", "1925")
    controller.add_book("1984", "George Orwell", "1949")

    # 移除书籍
    controller.remove_book("1984")

    # 显示书籍列表
    list_view.display_books(model.get_books())

通过这种设计,系统各个部分之间的耦合度降低,便于维护和扩展。例如,我们可以轻松添加新的视图类型(如图形化界面)而无需修改模型和控制器。

请仔细阅读以上代码,特别注意我们对模型(Model)的设计:LibraryModel类包含图书信息,并通知观察者视图进行更新。

既然例子中提到了观察者,我们顺带简单的介绍一下观察者模式在这里的使用,可以方便我们在后面学习中理解:

观察者模式概述
观察者模式是一种设计模式,其中一个对象(称为“主体”或“被观察者”)维护一组依赖于它的对象(称为“观察者”),并在自身状态发生变化时通知这些观察者。这个模式在MVC结构中非常常见,因为它能让模型(Model)在数据变化时自动通知视图(View)进行更新。

代码解析

  1. LibraryModel 类

在 LibraryModel 类中,模型包含图书信息,并维护一个观察者列表。当图书信息发生变化时,它会通知所有观察者。

class LibraryModel:
    def __init__(self):
        self.books = []  # 存储图书的列表
        self.observers = []  # 存储观察者的列表

    def add_book(self, book):
        self.books.append(book)
        self.notify_observers()  # 通知所有观察者

    def remove_book(self, book):
        self.books.remove(book)
        self.notify_observers()  # 通知所有观察者

    def get_books(self):
        return self.books

    def add_observer(self, observer):
        self.observers.append(observer)

    def remove_observer(self, observer):
        self.observers.remove(observer)

    def notify_observers(self):
        for observer in self.observers:
            observer.update()  # 调用每个观察者的 update 方法
  1. LibraryController 类
    在 LibraryController 类中,控制器处理用户输入并更新模型。当模型数据发生变化时,控制器作为观察者会收到通知,并调用相应的视图更新方法。
class LibraryController:
    def __init__(self, model, list_view, detail_view):
        self.model = model
        self.list_view = list_view
        self.detail_view = detail_view
        self.model.add_observer(self)  # 将控制器添加为模型的观察者

    def add_book(self, title, author, publication_date):
        book = Book(title, author, publication_date)
        self.model.add_book(book)

    def remove_book(self, title):
        book = next((b for b in self.model.get_books() if b.title == title), None)
        if book:
            self.model.remove_book(book)

    def update(self):
        self.list_view.display_books(self.model.get_books())  # 更新视图

观察者模式的工作流程

1.  添加观察者:
•   在 LibraryController 初始化时,调用 self.model.add_observer(self),将控制器自身添加为模型的观察者。
2.  通知观察者:
•   当模型中的数据发生变化(如添加或删除图书)时,模型调用 notify_observers 方法。
•   notify_observers 方法遍历所有观察者,并调用每个观察者的 update 方法。
3.  更新视图:
•   控制器实现了 update 方法,当它被模型调用时,会更新视图中的数据。例如,调用 self.list_view.display_books(self.model.get_books()) 更新图书列表视图。

此处我们再拓展一下:
MVC经常还会运用到别的设计模式,比如组合和策略模式

组合模式与MVC的结合

我们可以将组合模式用于视图(View)部分,通过组合不同的视图组件来构建复杂的用户界面。例如,我们可以将书籍视为基本组件,而类别视为包含书籍或其他子类别的组合组件。这样,我们可以创建一个层次结构的视图。

组合模式的实现

1.  LibraryComponent 类:定义了基本的接口。
2.  BookView 类:表示单个书籍。
3.  CategoryView 类:表示类别,可以包含书籍和子类别。
class LibraryComponent:
    def display(self, indent=0):
        pass

class BookView(LibraryComponent):
    def __init__(self, book):
        self.book = book

    def display(self, indent=0):
        print(" " * indent + f"Book: {self.book.title}, Author: {self.book.author}, Publication Date: {self.book.publication_date}")

class CategoryView(LibraryComponent):
    def __init__(self, name):
        self.name = name
        self.components = []

    def add(self, component):
        self.components.append(component)

    def remove(self, component):
        self.components.remove(component)

    def display(self, indent=0):
        print(" " * indent + f"Category: {self.name}")
        for component in self.components:
            component.display(indent + 2)

在控制器中使用组合视图
控制器中添加和删除书籍时,同时更新组合视图。

class LibraryController:
    def __init__(self, model, root_view):
        self.model = model
        self.root_view = root_view
        self.model.add_observer(self)

    def add_book(self, title, author, publication_date, category_name=None):
        book = Book(title, author, publication_date)
        self.model.add_book(book)
        if category_name:
            category_view = self.find_category(self.root_view, category_name)
            if category_view:
                category_view.add(BookView(book))
        else:
            self.root_view.add(BookView(book))
        self.update_view()

    def remove_book(self, title):
        book = next((b for b in self.model.get_books() if b.title == title), None)
        if book:
            self.model.remove_book(book)
            self.remove_book_view(self.root_view, title)
            self.update_view()

    def find_category(self, category_view, category_name):
        if category_view.name == category_name:
            return category_view
        for component in category_view.components:
            if isinstance(component, CategoryView):
                found = self.find_category(component, category_name)
                if found:
                    return found
        return None

    def remove_book_view(self, category_view, book_title):
        for component in category_view.components:
            if isinstance(component, BookView) and component.book.title == book_title:
                category_view.remove(component)
                return
            elif isinstance(component, CategoryView):
                self.remove_book_view(component, book_title)

    def update_view(self):
        self.root_view.display()

    def update(self):
        self.update_view()

示例运行代码

if __name__ == "__main__":
    # 创建模型、视图和控制器
    model = LibraryModel()
    root_view = CategoryView("Library")
    controller = LibraryController(model, root_view)

    # 创建类别
    fiction = CategoryView("Fiction")
    classics = CategoryView("Classics")
    modern_classics = CategoryView("Modern Classics")

    # 添加类别到根视图
    root_view.add(fiction)
    fiction.add(classics)
    fiction.add(modern_classics)

    # 添加书籍到类别
    controller.add_book("The Great Gatsby", "F. Scott Fitzgerald", "1925", "Classics")
    controller.add_book("1984", "George Orwell", "1949", "Classics")
    controller.add_book("To Kill a Mockingbird", "Harper Lee", "1960", "Modern Classics")

    # 显示类别和书籍
    controller.update_view()

    # 移除书籍
    controller.remove_book("1984")
    print("\nAfter removing '1984':")
    controller.update_view()

运行结果

Category: Library
  Category: Fiction
    Category: Classics
      Book: The Great Gatsby, Author: F. Scott Fitzgerald, Publication Date: 1925
      Book: 1984, Author: George Orwell, Publication Date: 1949
    Category: Modern Classics
      Book: To Kill a Mockingbird, Author: Harper Lee, Publication Date: 1960

After removing '1984':
Category: Library
  Category: Fiction
    Category: Classics
      Book: The Great Gatsby, Author: F. Scott Fitzgerald, Publication Date: 1925
    Category: Modern Classics
      Book: To Kill a Mockingbird, Author: Harper Lee, Publication Date: 1960

策略模式与MVC的结合

我们可以在控制器(Controller)中使用策略模式,以不同的排序策略来排序图书列表,并将排序后的结果传递给视图(View)。

策略模式在控制器中的实现

from abc import ABC, abstractmethod

class SortStrategy(ABC):
    @abstractmethod
    def sort(self, books):
        pass

class TitleSortStrategy(SortStrategy):
    def sort(self, books):
        return sorted(books, key=lambda book: book.title)

class AuthorSortStrategy(SortStrategy):
    def sort(self, books):
        return sorted(books, key=lambda book: book.author)

class PublicationDateSortStrategy(SortStrategy):
    def sort(self, books):
        return sorted(books, key=lambda book: book.publication_date)

在控制器中使用排序策略

class LibraryController:
    def __init__(self, model, category_view, strategy: SortStrategy):
        self.model = model
        self.category_view = category_view
        self.strategy = strategy
        self.model.add_observer(self)

    def set_strategy(self, strategy: SortStrategy):
        self.strategy = strategy

    def add_book(self, title, author, publication_date):
        book = Book(title, author, publication_date)
        self.model.add_book(book)
        self.update_view()

    def remove_book(self, title):
        book = next((b for b in self.model.get_books() if b.title == title), None)
        if book:
            self.model.remove_book(book)
            self.update_view()

    def update_view(self):
        sorted_books = self.strategy.sort(self.model.get_books())
        self.category_view.components = [BookView(book) for book in sorted_books]
        self.category_view.display()

    def update(self):
        self.update_view()

策略模式在MVC中的结合示例

if __name__ == "__main__":
    # 创建模型、视图和控制器
    model = LibraryModel()
    category_view = CategoryView("Library")
    controller = LibraryController(model, category_view, TitleSortStrategy())

    # 添加书籍
    controller.add_book("The Great Gatsby", "F. Scott Fitzgerald", "1925")
    controller.add_book("1984", "George Orwell", "1949")
    controller.add_book("To Kill a Mockingbird", "Harper Lee", "1960")

    # 显示书籍列表
    controller.update_view()

    # 切换排序策略
    controller.set_strategy(AuthorSortStrategy())
    print("\nBooks sorted by author:")
    controller.update_view()

    controller.set_strategy(PublicationDateSortStrategy())
    print("\nBooks sorted by publication date:")
    controller.update_view()

运行结果

Category: Library
  Book: 1984, Author: George Orwell, Publication Date: 1949
  Book: The Great Gatsby, Author: F. Scott Fitzgerald, Publication Date: 1925
  Book: To Kill a Mockingbird, Author: Harper Lee, Publication Date: 1960

Books sorted by author:
Category: Library
  Book: The Great Gatsby, Author: F. Scott Fitzgerald, Publication Date: 1925
  Book: To Kill a Mockingbird, Author: Harper Lee, Publication Date: 1960
  Book: 1984, Author: George Orwell, Publication Date: 1949

Books sorted by publication date:
Category: Library
  Book: The Great Gatsby, Author: F. Scott Fitzgerald, Publication Date: 1925
  Book: 1984, Author: George Orwell, Publication Date: 1949
  Book: To Kill a Mockingbird, Author: Harper Lee, Publication Date: 1960
•   组合模式(Composite Pattern):在视图层中,CategoryView 作为容器,可以包含多个 BookView 和 CategoryView,每个 CategoryView 可以进一步包含子类别和书籍。这使得我们能够创建一个层次结构的视图,展示图书和类别的关系。
•   策略模式(Strategy Pattern):在控制器层中,使用不同的排序策略(如按标题、作者和出版日期排序),根据用户的需求动态改变图书排序方式,并更新视图。
•   观察者模式(Observer Pattern):模型变化时通知控制器,控制器更新视图。

通过这种设计,我们将组合模式和策略模式整合到MVC架构中,实现了一个灵活、可扩展的图书管理系统。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容