前言
在面向对象编程中,如果我们编写了一个类,在使用类时通常需要进行实例化得到实例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)
,然后只改变部分属性,那效率岂不是杠杠的。这就需要应用原型模式。
模式定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象:
- 核心思想就是从一个对象创建另一个对象,不需要知道任何创建的细节。
模式构成
原型模式的核心就是克隆/拷贝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