设计模式之原型模式

43 阅读4分钟

前言

在面向对象编程中,如果我们编写了一个类,在使用类时通常需要进行实例化得到实例A,如果需要另一个实例,则再次实例化类得到实例B。如果类本身又拥有很多属性,那么实例化过程就显得比较重复冗余,如:

class Demo(object):
    a = 1
    b = 2
    c = ""

    def __init__(self, x, y):
        self.x = x
        self.y = y


A = Demo("x1", "y1")
A.a = 10
A.b = 20
A.c = "c"

B = Demo("x2", "y2")
B.a = 10
B.b = 20
B.c = "cc"

假如类属性非常多,而新的实例相比于另一个实例来说可能只有一个属性不同,这时又怎么办呢?重新实例化类,并从头开始对属性赋值也不是不可以,而设计模式中的原型模式可以帮我减少重复步骤。

需求假设

假设这样一个场景——女娲捏小泥人。小泥人有眼睛、鼻子、嘴巴、两只手、两只脚...如果每一个小泥人都从头到尾慢慢捏,那么多小泥人儿,得捏到天荒地老。如果可以直接对小泥人进行克隆(Clone),然后只改变部分属性,那效率岂不是杠杠的。这就需要应用原型模式。

模式定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象:

  • 核心思想就是从一个对象创建另一个对象,不需要知道任何创建的细节。

prototype.png

模式构成

原型模式的核心就是克隆/拷贝Clone:

  • 原型类Prototype: 声明一个克隆(Clone)自身的接口。
  • 具体原型类ConcretePrototype:实现一个克隆(Clone)自身的操作。

代码示例

golang

/*
 * File: prototype.go
 * Created Date: 2023-03-06 02:18:44
 * Author: ysj
 * Description:  原型类接口及其实现
 */

package main

import "fmt"

// 定义泥人儿,需要实现接口Clone
type ClayPerson interface {
    SetName(name string)
    SetTemper(temper string)
    SetEye(eye string)
    SetNose(nose string)
    SetSkin(Skin string)
    SetHands(hands int)
    SetGender(gender string)
    Introduction()
    Clone() ClayPerson
}

// 男娃娃小泥人儿
type ClayBoy struct {
    Name   string
    Eye    string
    Nose   string
    Skin   string
    Hands  int
    Gender string
    Temper string
}

// 克隆男娃娃小泥人儿
func (c *ClayBoy) Clone() ClayPerson {
    return &ClayBoy{
        Name:   c.Name,
        Eye:    c.Eye,
        Nose:   c.Nose,
        Skin:   c.Skin,
        Hands:  c.Hands,
        Gender: c.Gender,
        Temper: c.Temper,
    }
}

func (c *ClayBoy) SetName(name string) {
    c.Name = name
}
func (c *ClayBoy) SetTemper(temper string) {
    c.Temper = temper
}
func (c *ClayBoy) SetEye(eye string) {
    c.Eye = eye
}
func (c *ClayBoy) SetNose(nose string) {
    c.Nose = nose
}
func (c *ClayBoy) SetSkin(Skin string) {
    c.Skin = Skin
}
func (c *ClayBoy) SetHands(hands int) {
    c.Hands = hands
}
func (c *ClayBoy) SetGender(gender string) {
    c.Gender = gender
}

func (c *ClayBoy) Introduction() {
    fmt.Printf("%s: 性别%s, 有%s的眼睛, %s, %s的皮肤,%d只手,脾气%s。\n",
        c.Name, c.Gender, c.Eye, c.Nose, c.Skin, c.Hands, c.Temper,
    )
}

// 女娃娃小泥人儿
type ClayGirl struct {
    Name   string
    Eye    string
    Nose   string
    Skin   string
    Hands  int
    Gender string
    Temper string
}

// 克隆女娃娃小泥人儿
func (c *ClayGirl) Clone() ClayPerson {
    return &ClayGirl{
        Name:   c.Name,
        Eye:    c.Eye,
        Nose:   c.Nose,
        Skin:   c.Skin,
        Hands:  c.Hands,
        Gender: c.Gender,
        Temper: c.Temper,
    }
}

func (c *ClayGirl) SetName(name string) {
    c.Name = name
}
func (c *ClayGirl) SetTemper(temper string) {
    c.Temper = temper
}
func (c *ClayGirl) SetEye(eye string) {
    c.Eye = eye
}
func (c *ClayGirl) SetNose(nose string) {
    c.Nose = nose
}
func (c *ClayGirl) SetSkin(Skin string) {
    c.Skin = Skin
}
func (c *ClayGirl) SetHands(hands int) {
    c.Hands = hands
}
func (c *ClayGirl) SetGender(gender string) {
    c.Gender = gender
}

func (c *ClayGirl) Introduction() {
    fmt.Printf("%s: 性别%s, 有%s的眼睛, %s, %s的皮肤,%d只手,脾气%s。\n",
        c.Name, c.Gender, c.Eye, c.Nose, c.Skin, c.Hands, c.Temper,
    )
}
/*
 * File: main.go
 * Created Date: 2023-03-06 02:21:50
 * Author: ysj
 * Description:  golang 原型模式客户端调用
 */

package main

func main() {
    // 二狗
    ergou := &ClayBoy{
        Name:   "二狗",
        Eye:    "黑色",
        Nose:   "两个鼻孔",
        Skin:   "黄色",
        Hands:  2,
        Gender: "男",
        Temper: "很好",
    }
    ergou.Introduction()

    // 通过二狗子克隆出大壮, 大壮脾气不好
    dazhuang := ergou.Clone()
    dazhuang.SetName("大壮")
    dazhuang.SetTemper("暴躁")
    dazhuang.Introduction()

    // 小花
    xiaohua := &ClayGirl{
        Name:   "小花",
        Eye:    "黑色",
        Nose:   "两个鼻孔",
        Skin:   "黄色",
        Hands:  2,
        Gender: "女",
        Temper: "温柔",
    }
    xiaohua.Introduction()

    // 通过小花克隆出小朵,她们是双胞胎,只有名字不一样
    xiaoduo := xiaohua.Clone()
    xiaoduo.SetName("小朵")
    xiaoduo.Introduction()
}
$ go run .
二狗: 性别男, 有黑色的眼睛, 两个鼻孔, 黄色的皮肤,2只手,脾气很好。
大壮: 性别男, 有黑色的眼睛, 两个鼻孔, 黄色的皮肤,2只手,脾气暴躁。
小花: 性别女, 有黑色的眼睛, 两个鼻孔, 黄色的皮肤,2只手,脾气温柔。
小朵: 性别女, 有黑色的眼睛, 两个鼻孔, 黄色的皮肤,2只手,脾气温柔。

python

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
###
# File: prototype.py
# Created Date: 2023-03-06 03:09:23
# Author: ysj
# Description: 原型类接口及其实现
###

from abc import ABCMeta, abstractmethod
# from typing import Self


class ClayPerson(metaclass=ABCMeta):
    """抽象原型类"""
    # @abstractmethod
    # def clone() -> Self: # python3.11
    #     pass

    @abstractmethod
    def clone():
        pass


class ClayBoy(ClayPerson):
    """男娃娃小泥人儿"""

    def __init__(self,
                 name,
                 eye,
                 nose,
                 skin,
                 hands,
                 gender,
                 temper,
                 ):
        self.name = name
        self.eye = eye
        self.nose = nose
        self.skin = skin
        self.hands = hands
        self.gender = gender
        self.temper = temper

    def clone(self):
        self_clone = ClayBoy(
            name=self.name,
            eye=self.eye,
            nose=self.nose,
            skin=self.skin,
            hands=self.hands,
            gender=self.gender,
            temper=self.temper,
        )
        return self_clone

    def introduction(self):
        print("%s: 性别%s, 有%s的眼睛, %s, %s的皮肤,%d只手,脾气%s。" %
              (self.name, self.gender, self.eye, self.nose,
               self.skin, self.hands, self.temper)
              )


class ClayGirl(ClayPerson):
    """女娃娃小泥人儿"""

    def __init__(self,
                 name,
                 eye,
                 nose,
                 skin,
                 hands,
                 gender,
                 temper,
                 ):
        self.name = name
        self.eye = eye
        self.nose = nose
        self.skin = skin
        self.hands = hands
        self.gender = gender
        self.temper = temper

    def clone(self):
        self_clone = ClayGirl(
            name=self.name,
            eye=self.eye,
            nose=self.nose,
            skin=self.skin,
            hands=self.hands,
            gender=self.gender,
            temper=self.temper,
        )
        return self_clone

    def introduction(self):
        print("%s: 性别%s, 有%s的眼睛, %s, %s的皮肤,%d只手,脾气%s。" %
              (self.name, self.gender, self.eye, self.nose,
               self.skin, self.hands, self.temper)
              )
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
###
# File: main.py
# Created Date: 2023-03-06 03:20:27
# Author: ysj
# Description:  python 原型模式客户端调用
###

from prototype import ClayBoy, ClayGirl

# 二狗
ergou = ClayBoy(name="二狗", eye="黑色", nose="两个鼻孔",
                skin="黄色", hands=2, gender="男", temper="很好",
                )
ergou.introduction()

# 通过二狗子克隆出大壮, 大壮脾气不好
dazhuang = ergou.clone()
dazhuang.name = "大壮"
dazhuang.temper = "暴躁"
dazhuang.introduction()

# 小花
xiaohua = ClayGirl(name="小花", eye="黑色", nose="两个鼻孔",
                   skin="黄色", hands=2, gender="女", temper="温柔",
                   )
xiaohua.introduction()

# 通过小花克隆出小朵,她们是双胞胎,只有名字不一样
xiaoduo = xiaohua.clone()
xiaoduo.name = "小朵"
xiaoduo.introduction()
$ python3 main.py
二狗: 性别男, 有黑色的眼睛, 两个鼻孔, 黄色的皮肤,2只手,脾气很好。
大壮: 性别男, 有黑色的眼睛, 两个鼻孔, 黄色的皮肤,2只手,脾气暴躁。
小花: 性别女, 有黑色的眼睛, 两个鼻孔, 黄色的皮肤,2只手,脾气温柔。
小朵: 性别女, 有黑色的眼睛, 两个鼻孔, 黄色的皮肤,2只手,脾气温柔。

适用场景

原型模式适用于基于已有的对象快速创建出另一个对象的场景,无论是对象初始化时还是对象在运行时中。

特别是当对象初始化执行时间较长时,实例化另一个同类对象时原型模式尤其有用。

原型模式隐藏了对象创建的细节,减少了代码冗余,也提升了对象创建的性能。

参考资料

  • 程杰.大话设计模式[M].北京:清华大学出版社,2007.12