<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>设计模式归档 - 枫阿雨&#039;s blog</title>
	<atom:link href="https://www.crazyfay.com/tag/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.crazyfay.com/tag/设计模式/</link>
	<description>CrazyFay</description>
	<lastBuildDate>Sun, 28 May 2023 06:27:16 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.2</generator>

<image>
	<url>https://www.crazyfay.com/wp-content/uploads/2023/04/cropped-DockerGopher-32x32.png</url>
	<title>设计模式归档 - 枫阿雨&#039;s blog</title>
	<link>https://www.crazyfay.com/tag/设计模式/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>面向对象设计模式与Go语言实现 &#8211; 创建型模式</title>
		<link>https://www.crazyfay.com/2023/05/28/%e8%bd%af%e4%bb%b6%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b8%8ego%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0-%e5%88%9b%e5%bb%ba%e5%9e%8b%e6%a8%a1%e5%bc%8f/</link>
					<comments>https://www.crazyfay.com/2023/05/28/%e8%bd%af%e4%bb%b6%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b8%8ego%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0-%e5%88%9b%e5%bb%ba%e5%9e%8b%e6%a8%a1%e5%bc%8f/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Sun, 28 May 2023 05:17:53 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[设计模式]]></category>
		<guid isPermaLink="false">https://www.crazyfay.com/?p=349</guid>

					<description><![CDATA[<p>创建型模式（Creational Patterns） 设计模式概览 创建型模式（Creational Patt [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2023/05/28/%e8%bd%af%e4%bb%b6%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b8%8ego%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0-%e5%88%9b%e5%bb%ba%e5%9e%8b%e6%a8%a1%e5%bc%8f/">面向对象设计模式与Go语言实现 &#8211; 创建型模式</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h2>创建型模式（Creational Patterns）</h2>
<h3>设计模式概览</h3>
<ul>
<li>
<p><strong>创建型模式（Creational Patterns）：</strong></p>
<ol>
<li>
<p><strong>单例模式（Singleton）</strong></p>
</li>
<li>
<p><strong>工厂方法模式（Factory Method）</strong></p>
</li>
<li>
<p><strong>抽象工厂模式（Abstract Factory）</strong></p>
</li>
<li>
<p><strong>建造者模式（Builder）</strong></p>
</li>
<li>
<p><strong>原型模式（Prototype）</strong></p>
</li>
</ol>
</li>
<li>
<p>结构型模式（Structural Patterns）：</p>
<ol start="6">
<li>
<p>适配器模式（Adapter）</p>
</li>
<li>
<p>桥接模式（Bridge）</p>
</li>
<li>
<p>组合模式（Composite）</p>
</li>
<li>
<p>装饰者模式（Decorator）</p>
</li>
<li>
<p>外观模式（Facade）</p>
</li>
<li>
<p>享元模式（Flyweight）</p>
</li>
<li>
<p>代理模式（Proxy）</p>
</li>
</ol>
</li>
<li>
<p>行为型模式（Behavioral Patterns）：</p>
<ol start="13">
<li>
<p>责任链模式（Chain of Responsibility）</p>
</li>
<li>
<p>命令模式（Command）</p>
</li>
<li>
<p>解释器模式（Interpreter）</p>
</li>
<li>
<p>迭代器模式（Iterator）</p>
</li>
<li>
<p>中介者模式（Mediator）</p>
</li>
<li>
<p>备忘录模式（Memento）</p>
</li>
<li>
<p>观察者模式（Observer）</p>
</li>
<li>
<p>状态模式（State）</p>
</li>
<li>
<p>策略模式（Strategy）</p>
</li>
<li>
<p>模板方法模式（Template Method）</p>
</li>
<li>
<p>访问者模式（Visitor）</p>
</li>
</ol>
</li>
</ul>
<h3>0. 简单工厂模式</h3>
<blockquote>
<p>简单工厂模式（Simple Factory Pattern）</p>
<p>又叫做静态工厂方法模式（Static Factory Method Pattern），是一种对象创建型模式，并不属于GoF 的23种设计模式之一，但是是学习其他工厂模式的的基础。它定义了一个工厂类，用于创建不同类型的对象。简单来说，简单工厂模式就是将对象的创建过程封装起来，使得客户端可以通过一个工厂类来创建不同类型的对象，而无需知道具体的创建过程。</p>
<p>实现简单工厂，通常需要定义一个工厂方法来创建不同类型的对象。这个工厂方法通常是一个静态方法，它接收一个参数来指定要创建的对象类型，并返回一个实例化的对象。</p>
</blockquote>
<pre><code class="language-go">type MemTableType = int8
const (
    Btree MemTableType = iota
    ART
)

type MemTable interface {
    // Put Stores the Pos information for key pairs in the index
    Put(key, value []byte) bool

    // Get Retrieve the Pos information based on the key
    Get(key []byte) []byte

    // Del Delete the Pos information based on the key
    Del(key []byte) bool
}

type BTree struct {
    tree *btree.BTree
    lock *sync.RWMutex
}

// NewBTree Init BTree struct
func NewBTree() *BTree {
    return &amp;BTree{
        tree: btree.New(32),
        lock: new(sync.RWMutex),
    }
}

type AdaptiveRadixTree struct {
    tree art.Tree
    lock *sync.RWMutex
}

func NewAdaptiveRadixTree() *AdaptiveRadixTree {
    return &amp;AdaptiveRadixTree{
        tree: art.New(),
        lock: new(sync.RWMutex),
    }
}

func NewMemTable(typ MemTableType) MemTable {
    switch typ {
    case Btree:
        return NewBTree()
    case ART:
        return NewAdaptiveRadixTree()
    default:
        return NewBTree()
    }
}
func main()  {
    memTable := NewMemTable(Btree)
    memTable.Put([]byte(&quot;key&quot;), []byte(&quot;value&quot;))
}</code></pre>
<h3>1. 工厂方法模式</h3>
<blockquote>
<p><strong>工厂方法模式（Factory Method Pattern）</strong></p>
<p><strong>工厂方法模式也被称为虚拟构造器模式（Virtual Constructor Pattern）或多态工厂模式（Polymorphic Factory Pattern）</strong></p>
<p><strong>简单工厂模式虽然简单，但存在一个很严重的问题，违背了开闭原则。工厂方法模式，继承了简单工厂模式的优点，同时做出了修改以达到符合开闭原则的要求。在工厂方法模式中，不再提供一个统一的工厂类来创建所有的产品对象，而是针对不同的产品提供不同的工厂，系统提供一个与产品等级结构对应的工厂等级结构。</strong></p>
<p><strong>在很多场合有其实际应用，这种模式主要用于创建复杂对象，创建对象的逻辑可能包含一些业务需要的约束。</strong></p>
<p><strong>假设我们正在开发一个日志记录系统，这个系统可以将日志记录到不同的地方，例如，控制台，文件，或者是远程服务器。我们可以使用工厂方法模式来创建适合不同场景的日志记录器。</strong></p>
</blockquote>
<pre><code class="language-go">// Logger 是所有日志记录器的接口
type Logger interface {
    Log(message string)
}

// ConsoleLogger 是 Logger 的一个实现，它将日志记录到控制台
type ConsoleLogger struct{}

func (l ConsoleLogger) Log(message string) {
    fmt.Println(&quot;Console logger: &quot;, message)
}

// FileLogger 是 Logger 的一个实现，它将日志记录到文件
type FileLogger struct{}

func (l FileLogger) Log(message string) {
    fmt.Println(&quot;File logger: &quot;, message)
}

// LoggerFactory 是所有日志记录器工厂的接口
type LoggerFactory interface {
    CreateLogger() Logger
}

// ConsoleLoggerFactory 是 LoggerFactory 的一个实现，它创建 ConsoleLogger
type ConsoleLoggerFactory struct{}

func (f ConsoleLoggerFactory) CreateLogger() Logger {
    return ConsoleLogger{}
}

// FileLoggerFactory 是 LoggerFactory 的一个实现，它创建 FileLogger
type FileLoggerFactory struct{}

func (f FileLoggerFactory) CreateLogger() Logger {
    return FileLogger{}
}

func main() {
    var factory LoggerFactory

    factory = ConsoleLoggerFactory{}
    logger := factory.CreateLogger()
    logger.Log(&quot;This is a message&quot;)

    factory = FileLoggerFactory{}
    logger = factory.CreateLogger()
    logger.Log(&quot;This is another message&quot;)
}
</code></pre>
<h3>2. 抽象工厂模式</h3>
<blockquote>
<p><strong>抽象工厂模式（Abstract Factory Pattern）</strong></p>
<p><strong>抽象工厂模式是一种创建型设计模式，它提供了一种在不指定具体类的情况下创建一系列相关或相互依赖的对象的接口。抽象工厂允许你根据需求切换不同的具体工厂实现，以便在运行时根据配置或其他条件创建适当的对象。</strong><br />
<strong>简单来说，抽象工厂模式就是将多个工厂类的接口进行抽象，然后再用一个工厂类来封装这些工厂类的接口。在创建具体对象时，我们通常需要使用其他对象或者数据结构，因此我们还需要定义一些相关的产品接口和产品结构体。</strong></p>
</blockquote>
<pre><code class="language-go">// User 表示用户实体
type User struct {
    ID   int
    Name string
}

// UserDAO 定义了操作用户的接口
type UserDAO interface {
    GetUser(id int) (*User, error)
    SaveUser(*User) error
    DeleteUser(id int) error
}

// DBFactory 定义了抽象工厂接口
type DBFactory interface {
    CreateUserDAO() UserDAO
}

// MysqlUserDAO 实现了UserDAO接口，提供了对MySQL数据库的操作
type MysqlUserDAO struct{}

func (dao *MysqlUserDAO) GetUser(id int) (*User, error) {
    // 实现从MySQL数据库获取用户的具体操作
    // ...
    fmt.Println(&quot;Get user from MySQL database&quot;)
    return &amp;User{}, nil
}

func (dao *MysqlUserDAO) SaveUser(u *User) error {
    // 实现保存用户到MySQL数据库的具体操作
    // ...
    fmt.Println(&quot;Save user to MySQL database&quot;)
    return nil
}

func (dao *MysqlUserDAO) DeleteUser(id int) error {
    // 实现从MySQL数据库删除用户的具体操作
    // ...
    fmt.Println(&quot;Delete user from MySQL database&quot;)
    return nil
}

// MysqlFactory 实现了DBFactory接口，用于创建MysqlUserDAO
type MysqlFactory struct{}

func (f *MysqlFactory) CreateUserDAO() UserDAO {
    return &amp;MysqlUserDAO{}
}

// MongoUserDAO 实现了UserDAO接口，提供了对MongoDB数据库的操作
type MongoUserDAO struct{}

func (dao *MongoUserDAO) GetUser(id int) (*User, error) {
    // 实现从MongoDB数据库获取用户的具体操作
    // ...
    fmt.Println(&quot;Get user from MongoDB database&quot;)
    return &amp;User{}, nil
}

func (dao *MongoUserDAO) SaveUser(u *User) error {
    // 实现保存用户到MongoDB数据库的具体操作
    // ...
    fmt.Println(&quot;Save user to MongoDB database&quot;)
    return nil
}

func (dao *MongoUserDAO) DeleteUser(id int) error {
    // 实现从MongoDB数据库删除用户的具体操作
    // ...
    fmt.Println(&quot;Delete user from MongoDB database&quot;)
    return nil
}

// MongoFactory 实现了DBFactory接口，用于创建MongoUserDAO
type MongoFactory struct{}

func (f *MongoFactory) CreateUserDAO() UserDAO {
    return &amp;MongoUserDAO{}
}

func main() {
    mysqlFactory := &amp;MysqlFactory{}
    mongoFactory := &amp;MongoFactory{}

    userDAO := mysqlFactory.CreateUserDAO()
    userDAO.GetUser(1)
    userDAO.SaveUser(&amp;User{})
    userDAO.DeleteUser(1)

    userDAO = mongoFactory.CreateUserDAO()
    userDAO.GetUser(1)
    userDAO.SaveUser(&amp;User{})
    userDAO.DeleteUser(1)
}</code></pre>
<p>工厂方法模式和抽象工厂模式都属于创建型设计模式，它们都是用来创建对象的。但是，两者有一些重要的区别：</p>
<p><strong>工厂方法模式（Factory Method Pattern）：</strong></p>
<ul>
<li>工厂方法模式中的每个工厂只创建一种产品。</li>
<li>工厂方法模式主要用于创建一种类型的对象，但是这个对象的构造过程可能比较复杂。</li>
<li>在工厂方法模式中，客户端通常只需要知道具体工厂的接口，而不需要知道具体工厂类的类型。具体工厂类的类型可以在运行时通过配置或者其他方式决定。</li>
<li>例如，在上面的日志记录器的例子中，我们有一个日志记录器的工厂接口和多个实现这个接口的工厂类，每个工厂类都用于创建一种特定的日志记录器。</li>
</ul>
<p><strong>抽象工厂模式（Abstract Factory Pattern）：</strong></p>
<ul>
<li>抽象工厂模式中的每个工厂可以创建多种类型的产品。</li>
<li>抽象工厂模式主要用于创建相关的或者是依赖的对象组。对象组中的每个对象都是一个产品，但是这些产品需要一起工作以完成一些更大的功能。</li>
<li>在抽象工厂模式中，客户端通常只需要知道具体工厂的接口，而不需要知道具体工厂类的类型。具体工厂类的类型可以在运行时通过配置或者其他方式决定。</li>
<li>例如，在上面的数据库访问的例子中，我们有一个数据库访问对象的工厂接口和多个实现这个接口的工厂类，每个工厂类都用于创建一组相关的数据库访问对象，如用户数据访问对象，订单数据访问对象等。</li>
</ul>
<p>总的来说，如果你只需要创建一种类型的产品，那么使用工厂方法模式可能更简单；如果你需要创建多种类型的相关产品，那么使用抽象工厂模式可能更合适。</p>
<h3>3. 建造者模式</h3>
<blockquote>
<p><strong>建造者模式（Builder Pattern）</strong></p>
<p><strong>建造者模式是一种对象创建型模式，它可以将复杂对象的构建过程与表示分离开来，使得相同的构建过程可以创建不同的表示。</strong></p>
<p><strong>简单来说，建造者模式就是将一个复杂对象的创建过程基于链式调用封装起来，使得这个过程可以有不同的表示方式。</strong></p>
</blockquote>
<pre><code class="language-go">// RequestBuilder 是网络请求的建造者接口
type RequestBuilder interface {
    SetURL(url string) RequestBuilder
    SetMethod(method string) RequestBuilder
    SetHeader(key, value string) RequestBuilder
    SetBody(body string) RequestBuilder
    Build() *Request
}

// Request 是网络请求对象
type Request struct {
    URL     string
    Method  string
    Headers map[string]string
    Body    string
}

// HTTPRequestBuilder 是 HTTP 请求的具体建造者
type HTTPRequestBuilder struct {
    request *Request
}

// SetURL 设置请求的 URL
func (b *HTTPRequestBuilder) SetURL(url string) RequestBuilder {
    b.request.URL = url
    return b
}

// SetMethod 设置请求的方法
func (b *HTTPRequestBuilder) SetMethod(method string) RequestBuilder {
    b.request.Method = method
    return b
}

// SetHeader 设置请求的头信息
func (b *HTTPRequestBuilder) SetHeader(key, value string) RequestBuilder {
    b.request.Headers[key] = value
    return b
}

// SetBody 设置请求的消息体
func (b *HTTPRequestBuilder) SetBody(body string) RequestBuilder {
    b.request.Body = body
    return b
}

// Build 构建网络请求对象
func (b *HTTPRequestBuilder) Build() *Request {
    return b.request
}

// NewHTTPRequestBuilder 创建 HTTP 请求的建造者
func NewHTTPRequestBuilder() RequestBuilder {
    return &amp;HTTPRequestBuilder{
        request: &amp;Request{
            Headers: make(map[string]string),
        },
    }
}

func main() {
    // 创建一个 HTTP 请求的建造者
    builder := NewHTTPRequestBuilder()

    // 使用建造者设置请求的属性
    request := builder.
        SetURL(&quot;https://api.example.com&quot;).
        SetMethod(&quot;GET&quot;).
        SetHeader(&quot;Authorization&quot;, &quot;Bearer token&quot;).
        SetBody(&quot;data&quot;).
        Build()

    // 打印请求对象
    fmt.Println(request)
}</code></pre>
<h3>4. 原型模式</h3>
<blockquote>
<p><strong>原型模式（Prototype Pattern）</strong></p>
<p><strong>原型模式是一种创建型设计模式，它允许通过克隆现有对象来创建新对象，而无需通过实例化和配置新对象来完成。在原型模式中，我们创建一个原型对象，然后通过复制该对象来创建新的对象，而不是使用常规的构造函数和初始化流程。</strong></p>
<p><strong>应该注意原型模式不是用来获得性能优势的。它仅用于从原型实例创建新对象！</strong></p>
<p><strong>原型模式的实现需要满足以下要素：</strong></p>
<ul>
<li>
<p><strong>定义一个原型对象，该对象实现了 Clone() 方法，用于克隆自身并返回新的克隆对象。</strong></p>
</li>
<li>
<p><strong>定义一个工厂函数或方法，用于创建原型对象并初始化其属性。</strong></p>
</li>
<li>
<p><strong>客户端代码可以通过调用原型对象的 Clone() 方法来创建新的对象，而不是直接调用构造函数或工厂函数。</strong></p>
</li>
</ul>
</blockquote>
<pre><code class="language-go">// HTTPRequest 是HTTP请求的原型对象
type HTTPRequest struct {
    Method  string
    URL     string
    Headers map[string]string
    Body    []byte
}

// Clone 通过复制原型对象创建新的HTTP请求对象
func (r *HTTPRequest) Clone() *HTTPRequest {
    clone := *r
    return &amp;clone
}

// HTTPRequestPrototype 是HTTP请求的原型管理器
type HTTPRequestPrototype struct {
    prototypes map[string]*HTTPRequest
}

// Register 注册HTTP请求原型对象
func (p *HTTPRequestPrototype) Register(name string, request *HTTPRequest) {
    p.prototypes[name] = request
}

// Retrieve 根据名称从原型管理器中检索HTTP请求原型对象
func (p *HTTPRequestPrototype) Retrieve(name string) *HTTPRequest {
    prototype, ok := p.prototypes[name]
    if !ok {
        return nil
    }
    return prototype.Clone()
}

func main() {
    // 创建HTTP请求原型管理器
    prototypeManager := &amp;HTTPRequestPrototype{
        prototypes: make(map[string]*HTTPRequest),
    }

    // 注册不同的HTTP请求原型对象
    prototypeManager.Register(&quot;service1&quot;, &amp;HTTPRequest{
        Method: &quot;GET&quot;,
        URL:    &quot;http://service1.example.com&quot;,
        Headers: map[string]string{
            &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
    })

    prototypeManager.Register(&quot;service2&quot;, &amp;HTTPRequest{
        Method: &quot;POST&quot;,
        URL:    &quot;http://service2.example.com&quot;,
        Headers: map[string]string{
            &quot;Content-Type&quot;: &quot;application/xml&quot;,
        },
        Body: []byte(`&lt;data&gt;Hello, Service 2&lt;/data&gt;`),
    })

    // 启动中介服务
    http.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
        // 根据请求路径选择对应的HTTP请求原型对象
        name := r.URL.Path[1:] // 从路径中获取名称，例如 &quot;/service1&quot; 将匹配到 &quot;service1&quot;
        requestPrototype := prototypeManager.Retrieve(name)
        if requestPrototype == nil {
            http.NotFound(w, r)
            return
        }

        // 复制原型对象，并根据实际请求进行自定义修改
        request := requestPrototype.Clone()
        request.URL = fmt.Sprintf(&quot;%s%s&quot;, request.URL, r.URL.Path)

        // 发送请求并获取响应
        client := &amp;http.Client{}
        httpRequest, err := http.NewRequest(request.Method, request.URL, nil)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        // 设置请求标头
        for key, value := range request.Headers {
            httpRequest.Header.Set(key, value)
        }

        // 发送请求
        response, err := client.Do(httpRequest)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer response.Body.Close()

        // 读取响应内容
        body, err := ioutil.ReadAll(response.Body)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        // 将响应内容写入到HTTP响应中
        w.Header().Set(&quot;Content-Type&quot;, response.Header.Get(&quot;Content-Type&quot;))
        w.Write(body)
    })

    // 启动HTTP服务器，监听端口
    err := http.ListenAndServe(&quot;:8080&quot;, nil)
    if err != nil {
        fmt.Println(&quot;Failed to start server:&quot;, err)
    }
}</code></pre>
<h3>5. 单例模式</h3>
<blockquote>
<p><strong>单例模式（Singleton Pattern）</strong></p>
<p><strong>顾名思义,指的就是一个类只能生成一个实例，且该类能自行创建这个实例的一种模式。</strong><br />
<strong>在服务的生命周期内，一个类生成的一个实例对象只能存在一个，调用时复用该单例对象即可，这样既节省了内存空间,也节省了创建新对象时的资源消耗，也便于资源管理。</strong></p>
<p><strong>在日常开发中,很多场景其实都可以被设计成单例，像线程池、全局日志、数据库等等，单例模式总结下来有几个特点:</strong></p>
<ul>
<li><strong>单例类只有一个实例对象；</strong></li>
<li><strong>该单例对象必须由单例类自行创建；</strong></li>
<li><strong>单例类对外提供一个访问该单例的全局访问点</strong></li>
</ul>
</blockquote>
<p>单例模式一般有两种实现方式</p>
<p><strong>饿汉式（Eager Initialization）：</strong></p>
<ul>
<li>
<p>在程序启动时或单例类被加载时，就立即创建并初始化单例实例。</p>
</li>
<li>
<p>单例实例在整个程序生命周期中都存在，无论是否使用。</p>
</li>
<li>
<p>线程安全，因为在实例创建时就完成了初始化。</p>
</li>
<li>
<p>示例代码：</p>
<pre><code class="language-go">type singleton struct {
}

var _instance *singleton

func init() {
_instance = &singleton{}
}

func GetInstance() *singleton {
return _instance
}</code></pre>
</li>
</ul>
<p><strong>懒汉式（Lazy Initialization）：</strong></p>
<ul>
<li>
<p>在第一次调用获取单例实例的方法时，才创建并初始化单例实例。</p>
</li>
<li>
<p>单例实例是按需创建的，可能存在多个线程同时请求获取单例实例的情况。</p>
</li>
<li>
<p>需要考虑线程安全性，通常使用锁或其他同步机制来保证只有一个实例被创建。</p>
</li>
<li>
<p>示例代码：</p>
<pre><code class="language-go">type singleton struct {
}

var _instance *singleton
var once sync.Once

// GetInstance lazy
func GetInstance() *singleton {
once.Do(func() {
    _instance = &singleton{}
})
return _instance
}</code></pre>
</li>
</ul>
<h3>extra 配置模式</h3>
<blockquote>
<p>选项模式（Options Pattern）是一种常用的设计模式，得益于对函数式编程的支持，选项模式Go语言中经常使用。选项模式用于在函数或结构体中设置多个可选参数，以便在调用时提供更灵活的配置选项。</p>
</blockquote>
<pre><code class="language-go">type Options struct {
   Path    string
   MaxSize int
   Sync    bool
}

// DefaultOptions 返回默认配置选项
func DefaultOptions() *Options {
   return _defaultConf
}

var _defaultConf = &amp;Options{
   Path:    &quot;/temp/opt&quot;,
   MaxSize: 100,
   Sync:    true,
}

// NewOptions 处理配置选项
func NewOptions(opts ...func(*Options)) *Options {
   // 获取默认配置选项
   options := _defaultConf

   // 遍历传入的选项函数列表，逐个应用选项函数
   for _, opt := range opts {
      opt(options)
   }

   return options
}

func WithPath(value string) func(*Options) {
   return func(opts *Options) {
      opts.Path = value
   }
}

func WithMaxSize(value int) func(*Options) {
   return func(opts *Options) {
      opts.MaxSize = value
   }
}

func WithSync(value bool) func(*Options) {
   return func(opts *Options) {
      opts.Sync = value
   }
}

func main() {
   // 使用选项模式调用函数
   opt := NewOptions(
      WithPath(&quot;custom value&quot;),
      WithMaxSize(200),
      WithSync(false),
   )

   fmt.Println(opt)
}</code></pre>
<hr />
<p><img decoding="async" src="https://www.crazyfay.com/wp-content/uploads/2023/05/工厂方法.png" alt="工厂方法模式" /><br />
<img decoding="async" src="https://www.crazyfay.com/wp-content/uploads/2023/05/抽象工厂.png" alt="抽象工厂模式" /><br />
<img decoding="async" src="https://www.crazyfay.com/wp-content/uploads/2023/05/生成器.png" alt="建造者模式" /><br />
<img decoding="async" src="https://www.crazyfay.com/wp-content/uploads/2023/05/原型.png" alt="原型模式" /><br />
<img decoding="async" src="https://www.crazyfay.com/wp-content/uploads/2023/05/单例.png" alt="单例模式" /></p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2023/05/28/%e8%bd%af%e4%bb%b6%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b8%8ego%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0-%e5%88%9b%e5%bb%ba%e5%9e%8b%e6%a8%a1%e5%bc%8f/">面向对象设计模式与Go语言实现 &#8211; 创建型模式</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2023/05/28/%e8%bd%af%e4%bb%b6%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b8%8ego%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0-%e5%88%9b%e5%bb%ba%e5%9e%8b%e6%a8%a1%e5%bc%8f/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>面向对象设计模式与Go语言实现 – 面向对象设计原则</title>
		<link>https://www.crazyfay.com/2023/05/27/%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b8%8ego%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0-%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e8%ae%be%e8%ae%a1%e5%8e%9f/</link>
					<comments>https://www.crazyfay.com/2023/05/27/%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b8%8ego%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0-%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e8%ae%be%e8%ae%a1%e5%8e%9f/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Sat, 27 May 2023 02:13:22 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[设计模式]]></category>
		<guid isPermaLink="false">https://www.crazyfay.com/?p=353</guid>

					<description><![CDATA[<p>本系列文章是针对设计模式的系列文章，网上的很多设计模式的文章只讲了个大概和一部分纯理论，示例代码也是实际场景中 [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2023/05/27/%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b8%8ego%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0-%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e8%ae%be%e8%ae%a1%e5%8e%9f/">面向对象设计模式与Go语言实现 – 面向对象设计原则</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<p>本系列文章是针对设计模式的系列文章，网上的很多设计模式的文章只讲了个大概和一部分纯理论，示例代码也是实际场景中根本遇不到，往往只会出现在课本中的无生产意义的代码模型。本系列文章则希望在讲述设计模式的同时，采用更贴合生产实践中的代码，在实战中学习设计模式的相关概念，理解设计模式的精髓。</p>
<h2>面向对象设计原则</h2>
<p>关于面向对象设计原则众说纷纭，有人说是6种有人说是7种，但无论有几种实际上内容都是一致的。</p>
<p>本文将基于 亚历山大 · 什韦茨（Alexander Shvets）的《深入设计模式》中的分类，介绍面软件设计原则。</p>
<h3>1. 封装</h3>
<p>尽可能的封装变化的内容，即找到程序中的变化内容并将其与不变的内容区分开。该原则的主要目的是将变更造成的影响最小化。</p>
<p>可进行方法层面的封装与类层面的封装</p>
<h3>2. 面向接口</h3>
<p>面向接口进行开发， 而不是面向实现； 依赖于抽象类型，而不是具体类。</p>
<p>如果无需修改已有代码就能轻松对类进行扩展，那就可以说这样的设计是灵活的。</p>
<p>当你需要两个类进行合作时，可以让其中一个类依赖于另一个类。但是，还有另外一种更灵活的方式来设置对象之间的合作关系。</p>
<ol>
<li>确定一个对象对另一对象的确切需求：它需执行哪些方法？</li>
<li>在一个新的接口或抽象类中描述这些方法。</li>
<li>让被依赖的类实现该接口。</li>
<li>现在让有需求的类依赖于这个接口， 而不依赖于具体的类。你仍可与原始类中的对象进行互动，但现在其连接将会灵活得多。</li>
</ol>
<h3>3. 组合</h3>
<p>组合优于继承。继承可能是类之间最明显、最简便的代码复用方式。如果你有两个代码相同的类， 就可以为它们创建一个通用的基类，<br />
然后将相似的代码移动到其中。轻而易举！</p>
<p>不过，继承这件事通常只有在程序中已包含大量类，且修改任何东西都非常困难时才会引起关注。下面就是此类问题的清单。</p>
<ul>
<li><strong>子类不能减少超类的接口。</strong>你必须实现父类中所有的抽象方法，即使它们没什么用。</li>
<li><strong>在重写方法时，你需要确保新行为与其基类中的版本兼容。</strong>这一点很重要，因为子类的所有对象都可能被传递给以超类<br />
对象为参数的任何代码，相信你不会希望这些代码崩溃的。</li>
<li>继承打破了超类的封装，因为子类拥有访问父类内部详细内容的权限。此外还可能会有相反的情况出现，那就是程序员为了进一步扩展的方便而让超类知晓子类的内部详细内容。</li>
<li><strong>子类与超类紧密耦合。</strong>超类中的任何修改都可能会破坏子类的功能。</li>
<li><strong>通过继承复用代码可能导致平行继承体系的产生。</strong>继承通常仅发生在一个维度中。只要出现了两个以上的维度，你就必须创建数量巨大的类组合，从而使类层次结构膨胀到不可思议的程度。</li>
</ul>
<p>好在Golang在语言层面就解决了这个问题，因为Go语言中没有继承 XD</p>
<h3>4. SOLID原则</h3>
<p>SOLID 的五条原则是在罗伯特·马丁的著作《敏捷软件开发：原则、模式与实践》中首次提出的。SOLID 是让软件设计更易于理解、更加灵活和更易于维护的五个原则的简称。</p>
<h4>S：单一职责原则</h4>
<blockquote>
<p>单一职责原则（Single Responsibility Principle）</p>
<p>修改一个类的原因只能有一个。</p>
</blockquote>
<p>尽量让每个类只负责软件中的一个功能，并将该功能完全封装（你也可称之为隐藏）在该类中。<br />
这条原则的主要目的是减少复杂度。你不需要费尽心机地去构思如何仅用200 行代码来实现复杂设计，实际上完全可以使用十几个清晰的方法。<br />
当程序规模不断扩大、变更不断增加后，真实问题才会逐渐显现出来。到了某个时候，类会变得过于庞大，以至于你无法记住其细节。查找代码将变得非常缓慢，你必须浏览整个类，甚至整个程序才能找到需要的东西。程序中实体的数量会让你的大脑堆栈过载，你会感觉自己对代码失去了控制。<br />
还有一点：如果类负责的东西太多，那么当其中任何一件事发生改变时，你都必须对类进行修改。而在进行修改时，你就有可能改动类中自己并不希望改动的部分。</p>
<h4>O：开闭原则</h4>
<blockquote>
<p>开闭原则（open/closed Principle）</p>
<p>对于扩展， 类应该是“开放”的； 对于修改， 类则应是“封闭”的。</p>
</blockquote>
<p>本原则的主要理念是在实现新功能时能保持已有代码不变。</p>
<p>如果你可以对一个类进行扩展，可以创建它的子类并对其做任何事情（如新增方法或成员变量、重写基类行为等）， 那么它就是开放的。有些编程语言允许你通过特殊关键字（例如final ） 来限制对于类的进一步扩展， 这样类就不再是“开放”的了。如果某个类已做好了充分的准备并可供其他类使用的话（即其接口已明确定义且以后不会修改），那么该类就是封闭（你可以称之为完整）的。</p>
<p>如果一个类已经完成开发、测试和审核工作，而且属于某个框架或者可被其他类的代码直接使用的话，对其代码进行修改就是有风险的。你可以创建一个子类并重写原始类的部分内容以完成不同的行为，而不是直接对原始类的代码进行修改。这样你既可以达成自己的目标，但同时又无需修改已有的原始类客户端。</p>
<p>这条原则并不能应用于所有对类进行的修改中。如果你发现类中存在缺陷，直接对其进行修复即可，不要为它创建子类。<br />
子类不应该对其父类的问题负责。</p>
<h4>L：里氏替换原则</h4>
<blockquote>
<p>里氏替换原则（Liskov Substitution Principle）</p>
<p>当你扩展一个类时， 记住你应该要能在不修改客户端代码的情况下将子类的对象作为父类对象进行传递。</p>
</blockquote>
<p>这意味着子类必须保持与父类行为的兼容。在重写一个方法时，你要对基类行为进行扩展，而不是将其完全替换。<br />
替换原则是用于预测子类是否与代码兼容，以及是否能与其超类对象协作的一组检查。这一概念在开发程序库和框架时<br />
非常重要， 因为其中的类将会在他人的代码中使用——你是无法直接访问和修改这些代码的。<br />
与有着多种解释方式的其他设计模式不同，替代原则包含一组对子类（特别是其方法）的形式要求。</p>
<ul>
<li>
<p><strong>子类方法的参数类型必须与其超类的参数类型相匹配或更加抽象。</strong></p>
<p>即设计子类方法时，尽可能的将入参类型设置为父类的类型</p>
</li>
<li>
<p><strong>子类方法的返回值类型必须与超类方法的返回值类型或是其子类别相匹配。</strong></p>
<p>即设计子类方法时，尽可能的将入参类型设置为子类的类型</p>
</li>
<li>
<p><strong>子类中的方法不应抛出基础方法预期之外的异常类型</strong></p>
<p>在C++、Java语言中，如果抛出基础方法预期之外的异常类型，编译时会发生错误，而Golang、Rust等使用ErrCode处理错误的语言则需要额外注意返回错误的处理</p>
</li>
<li>
<p><strong>子类不应该加强其前置条件</strong></p>
<p>例如，基类的方法有一个<code>int</code>类型的参数。如果子类重写该方法时，要求传递给该方法的参数值必须为正数（如果该值为负则抛出异常）， 这就是加强了前置条件。客户端代码之前将负数传递给该方法时程序能够正常运行，但现在使用子类的对象时会使程序出错。</p>
</li>
<li>
<p><strong>子类不能削弱其后置条件</strong></p>
<p>假如你的某个类中有个方法需要使用数据库，该方法应该在接收到返回值后关闭所有活跃的数据库连接。<br />
你创建了一个子类并对其进行了修改，使得数据库保持连接以便重用。但客户端可能对你的意图一无所知。由于它认为<br />
该方法会关闭所有的连接，因此可能会在调用该方法后就马上关闭程序，使得无用的数据库连接对系统造成“污染”。</p>
</li>
<li>
<p><strong>超类的不变量必须保留</strong></p>
<p>不变量是让对象有意义的条件。因此，扩展一个类的最安全做法是引入新的成员变量和方法，而不要去招惹超类中已<br />
有的成员。当然在实际中，这并非总是可行。</p>
</li>
<li>
<p><strong>子类不能修改超类中私有成员变量的值</strong></p>
<p>有些编程语言允许通过反射机制来访问类的私有成员。还有一些语言（Python 和JavaScript）没有对私有成员进行任何保护。</p>
</li>
</ul>
<blockquote>
<p>其实总结一点就是，暴露给用户的总是父类/接口，所以在子类实现父类/接口的方法时，要以上层为准。</p>
</blockquote>
<h4>I：接口隔离原则</h4>
<blockquote>
<p>接口隔离原则（Interface Segregation Principle）</p>
<p>客户端不应被强迫依赖于其不使用的方法。</p>
</blockquote>
<p>尽量缩小接口的范围，使得客户端的类不必实现其不需要的行为。</p>
<p>根据接口隔离原则，你必须将“臃肿”的方法拆分为多个颗粒度更小的具体方法。客户端必须仅实现其实际需要的方法。否则，对于“臃肿”接口的修改可能会导致程序出错，即使客户端根本没有使用修改后的方法。</p>
<p>继承只允许类拥有一个超类，但是它并不限制类可同时实现的接口的数量。因此，你不需要将大量无关的类塞进单个接口。你可将其拆分为更精细的接口，如有需要可在单个类中实现所有接口，某些类也可只实现其中的一个接口。</p>
<p>请过度使用这条原则。不要进一步划分已经非常具体的接口。记住，创建的接口越多，代码就越复杂。因此要保持平衡。</p>
<h4>D：依赖倒置原则</h4>
<blockquote>
<p>依赖倒置原则（Dependency Inversion Principle）</p>
<p>高层次的类不应该依赖于低层次的类。两者都应该依赖于抽象接口。抽象接口不应依赖于具体实现。具体实现应该依赖于抽象接口。</p>
</blockquote>
<p>通常在设计软件时，你可以辨别出不同层次的类。</p>
<ul>
<li>低层次的类实现基础操作（例如磁盘操作、传输网络数据和<br />
连接数据库等）。</li>
<li>高层次类包含复杂业务逻辑以指导低层次类执行特定操作。</li>
</ul>
<p>有时人们会先设计低层次的类， 然后才会开发高层次的类。当你在新系统上开发原型产品时，这种情况很常见。由于低层次的东西还没有实现或不确定，你甚至无法确定高层次类能实现哪些功能。如果采用这种方式，业务逻辑类可能会更依赖于低层原语类。</p>
<p>依赖倒置原则通常和开闭原则共同发挥作用：你无需修改已有类就能用不同的业务逻辑类扩展低层次的类。</p>
<h2>面向对象设计原则与设计模式</h2>
<p>在1995 年，GoF（Gang of Four，四人组/四人帮）合作出版了《设计模式：可复用面向对象软件的基础》一书，共收录了 23 种设计模式，从此树立了软件设计模式领域的里程碑，人称「GoF设计模式」。<br />
这23种设计模式包括了三大类型，即<strong>创建型设计模式、结构型设计模式、行为型设计模式</strong>，以此为分类依据，这23种设计模式的概览如下：</p>
<ul>
<li>
<p>创建型模式（Creational Patterns）：</p>
<ol>
<li>
<p>单例模式（Singleton）</p>
</li>
<li>
<p>工厂方法模式（Factory Method）</p>
</li>
<li>
<p>抽象工厂模式（Abstract Factory）</p>
</li>
<li>
<p>建造者模式（Builder）</p>
</li>
<li>
<p>原型模式（Prototype）</p>
</li>
</ol>
</li>
<li>
<p>结构型模式（Structural Patterns）：</p>
<ol start="6">
<li>
<p>适配器模式（Adapter）</p>
</li>
<li>
<p>桥接模式（Bridge）</p>
</li>
<li>
<p>组合模式（Composite）</p>
</li>
<li>
<p>装饰者模式（Decorator）</p>
</li>
<li>
<p>外观模式（Facade）</p>
</li>
<li>
<p>享元模式（Flyweight）</p>
</li>
<li>
<p>代理模式（Proxy）</p>
</li>
</ol>
</li>
<li>
<p>行为型模式（Behavioral Patterns）：</p>
<ol start="13">
<li>
<p>责任链模式（Chain of Responsibility）</p>
</li>
<li>
<p>命令模式（Command）</p>
</li>
<li>
<p>解释器模式（Interpreter）</p>
</li>
<li>
<p>迭代器模式（Iterator）</p>
</li>
<li>
<p>中介者模式（Mediator）</p>
</li>
<li>
<p>备忘录模式（Memento）</p>
</li>
<li>
<p>观察者模式（Observer）</p>
</li>
<li>
<p>状态模式（State）</p>
</li>
<li>
<p>策略模式（Strategy）</p>
</li>
<li>
<p>模板方法模式（Template Method）</p>
</li>
<li>
<p>访问者模式（Visitor）</p>
</li>
</ol>
</li>
</ul>
<p>设计模式是践行面向对象设计原则的良好案例，本系列文章将按照这三种分类，讲述这23种经典的设计模式，并适当扩展在此23种设计模式之外的常用设计模式</p>
<h2>写在后面</h2>
<p>面向对象设计原则与设计模式，不一定总是适用的，在一些复杂场景下，运用设计模式可以将系统解耦，增加系统的可维护性、可拓展性，降低系统的复杂度。但是过度运用设计模式反而会大大增加系统的复杂度和理解难度，所以请适当使用设计模式。<br />
而且面向对象中的很多设计都过于理想化，实际生产过程中很大一部分都没有完全利用到面向对象理想中的最大的优势，因为将一个系统模块进行面向对象的抽象见面在一些复杂场景下往往是极具挑战性，或者说从实践角度上来说是意义不大的。正如如今在服务端开发领域用的最多的MVC模型，其本质上就是典型的面向过程的贫血模型，而真正面向对象充血模式的DDD，多数是难以落地的，多是进行进一步封装抽象，过度追求面向对象往往只会徒增系统复杂度。<br />
所以说，学习设计模式的过程是一个则其善者而从之，其不善者而改之的过程，软件设计没有银弹，只有在合适的场合运用合适的设计。</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2023/05/27/%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b8%8ego%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0-%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e8%ae%be%e8%ae%a1%e5%8e%9f/">面向对象设计模式与Go语言实现 – 面向对象设计原则</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2023/05/27/%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b8%8ego%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0-%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e8%ae%be%e8%ae%a1%e5%8e%9f/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
