设计模式—原型模式(Prototype)
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
目录
一、什么是原型模式
用原型实例指定创建对象的种类并且通过拷贝这些原型创建新的对象。
原型模式其实就是从一个对象再创建另外一个可定制的对象而且不需知道任何创建的细节
简单说就是先创建一个原型类实例然后通过克隆的方法来复制一个一样的新对象这个对象和原来的对象相同或相似
核心通过自带的Clone方法和ICloneable接口创建跟该对象相同的新对象不需要知道具体的创建细节。
二、原型模式具有什么优缺点吗
- 隐藏对象创建细节提高性能一般在初始化的信息不发生变化的情况下克隆是最好的办法。这既隐藏了对象创建的细节又对性能是大大的提高。
- 不用重新初始化对象而是动态地获得对象运行时的状态
三、有什么缺点
- 根据实际应用需要涉及到深浅复制问题
- 每一个类都需要一个Clone方法
- Clone方法位于类的内部当对已有类进行改造的时候需要修改代码违背开闭原则
四、什么时候用原型模式
- 同一个类当需要实例化多次的时候
- 对象之间相同或相似即只是个别的几个属性不同的时候
五、代码展示
场景小菜最近在找工作需要复印大量的简历发给求职公司小菜和大鸟通过复制简历这个事情来谈到了了六七年前和现在复印简历的不同六七年前简历一般都是手写很珍贵现在只要有一份简历原版直接复印就可以了。对于编程来说直接Ctrl+C和Ctrl+V直接复制代码就可以了但简单的复制代码有时候也会带来重复代码的灾难的。大鸟要求小菜写一个简历类里面必须有姓名、性别、年龄、工作经历最后要求有三份简历。
①、简历代码初步实现
简历类
//简历
class Resume
{
private string name; //姓名
private string sex; //性别
private string age; //年龄
private string timeArea; //工作时间
private string company; //公司
public Resume(string name) //有参的构造方法
{
this.name = name; //赋值
}
//设置个人信息
public void SetPersonalInfo(string sex,string age)
{
this.sex = sex; //赋值
this.age = age; //赋值
}
//设置工作经历
public void SetWorkExperience(string timeArea,string company)
{
this.timeArea = timeArea; //赋值
this.company = company; //赋值
}
//显示
public void Display()
{
Console.WriteLine("{0}{1}{2}", name, sex, age); //姓名性别年龄
Console.WriteLine("工作经历{0}{1}", timeArea, company); //工作经历工作时间公司
}
}
客户端调用代码
class Program
{
static void Main(string[] args)
{
#region 第一种方法
//Resume a = new Resume("大鸟 "); //实例化简历对象
//a.SetPersonalInfo("男", "29"); //设置个人信息
//a.SetWorkExperience("1998-2000 ", "xx公司"); //设置工作经历
//Resume b = new Resume("大鸟 ");
//b.SetPersonalInfo("男 ", "29");
//b.SetWorkExperience("1998-2000 ", "xx公司");
//Resume c = new Resume("大鸟 ");
//c.SetPersonalInfo("男 ", "29 ");
//c.SetWorkExperience("1998-2000 ", "xx公司");
#endregion
缺点每new一次都需要执行一次构造函数如果构造函数的执行时间很长那么多次的执行这个初始化操作就很低效。
三份简历就需要实例化3次如果是需要20份简历就需要实例化20次如果98写成99年就需要改20次。
每次实例化出来的对象都存放在堆内存里面按照这样的方式实例化对堆内存的消耗就非常大。
如何改进这个问题呢可以使用第二种方法先实例化出来一个对象a然后把a引用所指向的地址赋值给b和c这样在堆内存里面
#region 第二种方法
Resume a = new Resume("大鸟 "); //实例化简历对象
a.SetPersonalInfo("男", "29"); //设置个人信息
a.SetWorkExperience("1998-2000 ", "xx公司"); //设置工作经历
Resume b = a;
Resume c = a;
#endregion
a.Display(); //显示
b.Display();
c.Display();
Console.ReadKey();
}
}
如何解决Clone克隆
②、原型模式
Prototype类
abstract class Prototype //抽象原型类
{
private string id; //私有的成员变量
public Prototype(string id) //有参的构造方法
{
this.id = id;
}
public string Id //属性
{
get { return id; } //取值
}
public abstract Prototype Clone(); //抽象克隆方法
}
ConcretePrototype1
class ConcretePrototype1 : Prototype //继承原型类
{
private string id;
public ConcretePrototype1(string id) : base(id) { } //重写有参的构造方法
public override Prototype Clone() //重写原型类抽象方法
{
//创建当前对象的浅表副本
return (Prototype)this.MemberwiseClone(); //返回Prototype类型的克隆对象括号里的作用是指定要创建的对象种类
}
}
③、简历的原型实现
思想
Resume:
- 记录相关属性、方法、变量
- Clone方法
客户端
- 实例化一个原型对象
- 调用要复制类的Clone()方法进行复制并返回复制完的对象
方法创建一个新对象然后将当前对象的非静态字段复制到该新对象
复制类型this.MemberwiseClone();
值类型逐位复制
引用类型复制引用但不赋值引用的对象
- 可修改简历
- 显示
简历类
class Resume:ICloneable
{
private string name; //姓名
private string sex; //性别
private string age; //年龄
private string timeArea; //工作时间
private string company; //公司
public Resume(string name) //有参的构造方法
{
this.name = name; //赋值
}
//设置个人信息
public void SetPersonalInfo(string sex, string age)
{
this.sex = sex; //赋值
this.age = age; //赋值
}
//设置工作经历
public void SetWorkExperience(string timeArea, string company)
{
this.timeArea = timeArea; //赋值
this.company = company; //赋值
}
//显示
public void Display() //实现接口的方法用来克隆对象
{
Console.WriteLine("{0}{1}{2}", name, sex, age); //姓名性别年龄
Console.WriteLine("工作经历{0}{1}", timeArea, company); //工作经历工作时间公司
}
public Object Clone() //克隆的方法
{
return (Object)this.MemberwiseClone();
}
}
客户端
static void Main(string[] args)
{
Resume a = new Resume("大鸟"); //实例化简历类调用Resume类有参的构造方法
a.SetPersonalInfo("男", "29"); //调用SetPersonalInfo方法并传值
a.SetWorkExperience("1998-2000", "xx公司"); //调用SetWorkExperience方法并传值
//调用Clone方法实现新简历的生成并且可以再修改新简历的细节
Resume b = (Resume)a.Clone(); //克隆a对象并赋值给简历对象b此时a和b一样
b.SetWorkExperience("1998-2006", "yy公司"); //b对象可以修改自己简历的细节
Resume c = (Resume)a.Clone(); //克隆a对象给简历对象c
c.SetPersonalInfo("男", "24"); //c对象可以修改自己简历的细节
a.Display(); //显示简历a
b.Display(); //显示简历a
c.Display(); //显示简历a
Console.ReadKey(); //等待用户按下某键退出
}
问题‘简历’对象里的数据都是string类型的如果类当中有对象引用那么引用的对象数据是不会被克隆过来的。
④、深复制
WorkExperience类
//工作经历类
class WorkExperience : ICloneable
{
private string workDate;
public string WorkDate
{
get { return workDate; }
set { workDate = value; }
}
private string company;
public string Company
{
get { return company; }
set { company = value; }
}
public Object Clone() //让“工作经历”类实现克隆方法
{
return (Object)this.MemberwiseClone();
}
}
Resume类
// 简历类
class Resume:ICloneable
{
private string name;
private string age;
private string sex;
private WorkExperience work;
public Resume (string name) //有参的构造方法
{
this.name = name; //赋值姓名
work = new WorkExperience(); //在“简历”类实例化时同时实例化“工作经历”
}
//提供Clone方法调用的私有构造函数以便克隆“工作经历”的数据
private Resume(WorkExperience work)
{
this.work = (WorkExperience)work.Clone();
}
public void SetPersonalInfo(string sex,string age) //设置个人信息
{
this.sex = sex; //赋值性别
this.age = age; //赋值年龄
}
public void SetWorkExperience (string workDate,string company) //设置工作经历
{
work.WorkDate = workDate; //赋值工作时间
work.Company = company; //赋值公司
}
public void Display() //显示
{
Console.WriteLine("{0} {1} {2}", name, sex, age);
Console.WriteLine("工作经历{0}{1}", work.WorkDate, work.Company);
}
public Object Clone() //克隆方法
{
//调用私有的构造方法让“工作经历”克隆完成
//然后再给这个“简历”对象的相关字段赋值
//最终返回一个深复制的简历对象
Resume obj = new Resume(this.work);
obj.name = this.name;
obj.sex = this.sex;
obj.age = this.age;
return obj;
}
}
⑤、浅复制
WorkExperience类
//工作经历
class WorkExperience
{
private string workDate; //字段时间区间
public string WorkDate //属性时间区间
{
get { return workDate; } //读
set { workDate = value; } //写
}
private string company; //字段公司
public string Company //属性公司
{
get { return company; } //读
set { company = value; } //写
}
}
Resume类
class Resume
{
private string name; //姓名
private string sex; //性别
private string age; //年龄
private WorkExperience work; //工作经历
public Resume(string name) //构造方法
{
this.name = name; //赋值姓名
work = new WorkExperience(); //在“简历”类实例化时同时实例化“工作经历”
}
public void SetPersonalInfo(string sex, string age) //设置个人信息
{
this.sex = sex; //赋值性别
this.age = age; //赋值年龄
}
public void SetWorkExperience(string workDate, string company) //设置工作经历
{
work.WorkDate=workDate; //赋值工作时间
work.Company=company; //赋值公司
}
public void Display() //显示
{
Console.WriteLine("{0} {1} {2}", name, sex, age); //输出姓名性别年龄
Console.WriteLine("工作经历{0}{1}", work.WorkDate, work.Company); //输出工作经历工作时间公司
}
public Object Clone() //克隆
{
return (Object)this.MemberwiseClone(); //返回Object类型的克隆对象
}
}
客户端程序代码
Resume a = new Resume("大鸟"); //实例化一个叫大鸟的简历对象调用有参构造方法
a.SetPersonalInfo("男", "29"); //调用方法赋值性别年龄
a.SetWorkExperience("1998-2000", "xx公司"); //调用方法赋值工作时间公司
Resume b = (Resume)a.Clone(); //克隆对象a给b
b.SetWorkExperience("1998-2006", "yy公司"); //修改细节
//b和c都克隆于a但当它们都设置了“工作经历”时我们希望的结果是三个的显示不一样
Resume c = (Resume)a.Clone(); //克隆对象a 给c
c.SetPersonalInfo("男", "24"); //调用方法赋值性别年龄
c.SetWorkExperience("1998-2003", "zz公司"); //修改细节
a.Display(); //显示简历a
b.Display(); //显示简历b
c.Display(); //显示简历c
Console.ReadKey();
它在内存里面的形式是这样的。
六、涉及到的知识点
1、一个类里面有哪些东西
2、类和实例
什么是类
就是具有相同的属性和功能的对象的抽象的集合。注意
- 类名称首字母大写。多个单词则各个首字母大写
- 对外公开的方法需要用‘public’修饰符。
什么是实例
就是一个真实的对象。比如我们都是‘人’而你和我其实就是‘人’类的实例了。
什么是实例化
创建对象的过程使用new关键字来创建。
Cat cat = new Cat(); //其实做了两件事情
Cat cat; //第一步、声明一个Cat的对象对象名为cat
cat = new Cat(); //第二步、将此cat对象实例化
3、字段和属性
什么是字段
是存储类要满足其设计所需要的数据字段是与类相关的变量。
private string name = ""; //name就是一个字段私有的类变量
注意
- 如果在定义字段时在字段的类型前面使用了readonly关键字那么字段就为只读字段它只能在以下两个位置被赋值或者传递到方法中被改变。
- 在定义字段时赋值
- 在类的构造函数内被赋值或传递到方法中被改变而且在构造函数中可以被多次赋值。
属性是什么
是一个方法或一对方法体。提供对类或对象的访问。
属性怎么用呢
它有两个方法get和set。
get访问器从属性获取值。返回与声明的属性相同的数据类型表示的意思是调用时可以得到内部字段的值或引用
set访问器为属性赋值。没有显式设置参数但它有一个隐式参数用关键字value表示它的作用是调用属性时可以给内部的字段或引用赋值。
属性有什么作用
限制外部类对类中成员的访问权限定义在类级别上。
private int _age; //年龄
public int Age
{
get //也可以直接在属性中进行判断操作、设置限制
{
if (_age >= 0 && _age <= 150) //如果年龄大于 0并且小于150的表示输入正确
{
return _age; //则返回输入的年龄
}
else //否则表示输入错误
{
return 18; //返回指定年龄18
}
}
set { _age = value; }
}
静态属性是什么
在属性前面加static关键字这个属性就成为了静态属性。
有什么作用呢
- 不管类是否有实例它们都是存在的。
- 当从类的外部访问时必须使用类名引用而不是实例名。
class Person
{
private static string name; //字段
public static string Name //属性
{
get { return name; }
set { name = value; }
}
}
static void Main(string[] args)
{
Person.Name = "小菜"; //不需要实例化Person类即可直接对属性赋值
}
属性和字段的公有部分解释
内存
- 字段分配内存
- 属性不分配内存
命名规范
- 字段Camel大小写
- 属性Pascal小写
4、修饰符
在变量前面可以加上访问修饰符readonly、static
readonly只读读取该字段的值不能给字段赋值
static静态字段可以直接通过类名访问该字段
5、访问修饰符有哪些
- public公有的对任何类可访问
- private私有的只在类的内部可以访问成员默认是这个
- protected保护的只允许该类的派生类访问
- internal内部的同一项目所有类可访问
6、this关键字传递的是什么
- 用于区分类的成员和本地变量或参数
- 作为调用方法的实参
//this调用成员变量或成员方法
class Person
{
private string name; //字段
public void setName(string name) //方法
{
this.name = name; //将参数值赋予类中的成员变量
}
//成员变量和setName()方法中的形式参数的名称相同都为name那么如何区分使用的是哪一个变量呢
//使用this关键字代表本类对象的引用this.name指Person类中name成员变量等号后面的name指传过来的形参name
}
this作为方法的返回值
public Book getBook()
{
return this; //返回Book类引用
}
在getBook类中方法的返回值为Book类所以方法体中使用return this这种形式将Book类的对象返回
this关键字和对象之间有什么关系
this引用的就是本类的一个对象。
如果省略this会怎么样
直接写成name=name只是把形参name赋值给参数变量本身而已成员变量name的值没有改变
7、构造方法
- 什么时候用就是对类进行初始化在创建该类的对象时就会调用。
- 有哪些特点与类同名
无返回值
不需要void在new时候调用
//希望每个小猫一诞生就有姓名
class Cat
{
private string name =""; //声明Cat类的私有字符串变量name
public Cat(string name) //定义Cat类的构造方法参数是输入一个字符串
{
this.name =name; //将参数赋值给私有变量name
}
public string Shout()
{
return "我的名字叫"+name+" 喵";
}
}
注所有类都有构造方法如果你不编码则系统默认生成空的构造方法若有定义的构造方法那么默认的构造方法就会失效这个构造方法什么也不做只是为了让用户能够顺利地实例化
8、方法重载
是什么指方法名相同但参数的数据类型、个数或顺序不同的方法。一同二不同
有什么好处在不改变原方法的基础上新增功能。
class Animal
{
private string name;
//方法重载方法名相同、数据类型、个数/顺序不同
public Animal{} //无参的构造方法
public Animalstring name //有参的构造方法
{
this.name = name;
}
}
9、抽象类
什么是抽象类
目的抽取相同代码实现封装思想
特点
- 抽象类不能实例化
- 抽象方法是必须被子类重写的方法
- 如果类中包含抽象方法那么类就必须定义为抽象类不论是否还包含其他一般方法
什么是重写
将父类实现替换为它自己的实现
虚成员 | 抽象成员 | |
关键字 | virtual | abstract |
实现体 | 有实现体 | 没有实现体被分号取代 |
在派生类中被覆写 | 可重写也可不重写使用override | 必须被重写使用override |
10、值类型和引用类型
值类型修改其中一个对另一个不会有影响在栈上存的是值
引用类型对第二个变量修改会影响到第一个变量。因为他们指向同一个引用。在栈上存的是地址堆上存的是值