(13)C#传智:访问修饰符,简单工厂模式,序列化与反序列化,部分类,密封类,接口(第13天)
Posted dzweather
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(13)C#传智:访问修饰符,简单工厂模式,序列化与反序列化,部分类,密封类,接口(第13天)相关的知识,希望对你有一定的参考价值。
内容超级多,慢慢来。。。
深入BinaryFormatter
一、访问修饰符
public: 公共的,公开的
private:私有的,只能在当前类的内部访问
protected:受保持的,只能在当前类的内部和该类的子类中访问。
internal:只能在当前程序集(项目)中访问。
同一项目中public与internal权限一样。
protected internal:从当前程序集或派生自包含类的类型访问受保护的内部成员。
internal的感观认识:
在同一个解决方案中,
a)在Test项目中添加两个类:
internal class Person
public class Student
b)再新建一个项目Luck,若想从Luck项目中访问Test项目中的Person与Student类,
首先在Luck项目(在右侧解决方案中右击Luck项目目录中的“引用”),添加
引用,弹出的窗体中选择Test项目(窗体左侧解决方案中的“项目”中)
第二步,在Luck项目的最上面添加命名空间:using Luck;
第三步,在Luck内部就可以直接使用上面Student进行声明赋值,注意的是,
鼠标指向上面声明代码时,会提示是Test.Student,说明是Test项目的.
但是此时用Person p=new Person();时会发生错误,说明不能访问到项目
Test中的internal Person。因为internal只能在项目内使用,跨项目会
提示错误。这就是public与internal的区别。
protected internal感观认识:
实际上是两种方式都可以用,比较灵活。
既可是internal即项目内访问,也可以protected在本类及子类中访问(可能跨项目)
实例:在同一解决项目中有两个项目Test与Luck,其中Test中引用Luck。
Luck项目:
namespace Luck
public class BaseClass
protected internal int myValue = 0;
internal class TestAccess
private void Access()
var baseObject = new BaseClass();
//internal同项目,所以下句可以访问
baseObject.myValue = 5;
Test项目,主程序
using System;
using Luck;
namespace Test
internal class Program : BaseClass //继承
private static void Main(string[] args)
//引用后,可访问Luck项目中的public BaseClass
var b = new BaseClass();
//Program继承于BaseClass,可访问父类的proctected myValue
var InheritObject = new Program();
InheritObject.myValue = 10;//因继承而能访问
b.myValue = 10;//错误,跨项目只能用继承才能访问
Console.ReadKey();
internal与protected的访问权限谁大?
internal仅限于本项目内,出了该项目就不能访问。
protected是本类及子类,可能没出本项目,也可能出了本项目,只要该类或本类
都可访问。
所以两者各有优势,没有谁大谁小之说。
1)能够修饰类的访问修饰符只有两个:public,internal
默认的修改是internal,所以一般需人为添加为public。
2)可访问性不一性
子类的访问权限不能高于父类的访问权限。
例:internal class Person
public class Student:Person
错误,子类权限大于父类本想限制在本项目内的权限,会暴露父类成员。
二、简单工厂设计模式
设计模式:设计这个项目的一种方式。程序的巧妙写法,不是算法,是表达方式。
如同文章写法:总分结构、分总结构、总分总结构、倒叙结构...等等
程序表达一样,有很多通用的表达方法,把一些相同的表达方法归纳总结成一种,这种就
形成一种设计模式。同样另一些相同的归纳成另一种设计模式。以此类推...
《C#设计模式》,23种,从头到尾敲一篇。
设计模式是软件开必过程中经验的积累,特定问题的经过实践的特定方法。
先看后面实例后,再看前面的文字。。。
1、什么是工厂?
1)客户不需要知道怎么做的, 但是不影响使用
我们身边有很多工厂:酿酒的酒厂, 制衣的衣厂, 加工肉类的肉加工厂等等.这些工厂他
们到底是怎么酿酒的? 怎么制衣的?怎么加工肉的? 我们并不知道, 也不需要知道. 不知
道并不影响我们喝酒, 穿衣, 吃肉. 这就是工厂的特点之一.
2)给你材料, 你去制造出我想要的东西, 至于怎么做, 我并不关心.
比如肉加工厂---双汇. 牛肉进去出来牛肉火腿肠, 羊肉进去出来羊肉火腿肠, 猪肉进去出
来猪肉火腿肠. 我不需要知道怎么加工的, 我只需要把材料扔进去, 然后对应的火腿肠就
出来了. 这就是工厂的第二个特点。
2、什么是设计模式?
我们基本都知道设计模式有23种。
设计模式不是语法, 而是一种巧妙的写法, 能够把程序变得更加灵活的写法.
设计模式有三种: 创建型, 行为型, 结构型. 简单工厂设计模式属于创建型.
但简单工厂设计模式不属于23种设计模式范围内, 属于23种设计模式中工厂设计模式
里最简单的一种。
3、什么是简单工厂模式?
简单工厂设计模式, 又叫做静态工厂设计模式.
简单工厂设计模式提供一个创建对象实例的功能,而无需关心其具体实现,被创建实例
的类型可以是接口、抽象类,也可以是具体的类。
4、简单工厂设计模式的四个要素
这个很重要, 这也是创建一个简单工厂的步骤:
1)API接口: 创建一个API接口或抽象类
2)Impl实现类: 一个或多个实现API接口/抽象类的类
3)工厂: 定义一个工厂, 用来创建API接口类对象
4)客户端: 用来调用工厂创建API接口或抽象类的客户端
5、简单工厂创建过程:
第一步: 定义API接口或抽象类, 并定义一个operate操作方法;
第二步: 定义API的实现类, 每个实现类单独实现operate方法;
第三步: 定义工厂类. 工厂类依赖API接口和API的实现类, 简单工厂设计模式是创建型的,
通常是用来创建实体类. 因此我们定义一个create方法, 来创建实例对象,入参通常
是一个指定的类型.
第四步: 定义客户端. 客户端传入一个指定的类型给工厂, 工厂就会创建出对应的实现类.
6、简单工厂设计模式例子
用户需要指定品牌笔记本。
工厂万能满足用户需要。
两者之间,通过工厂来实现。通过特定品牌创建子类,返回通用的父类(子类转换而来)
工厂实现的前提:做好接口或抽象类,并在子类中实现。
用户需求笔记本: 用户需求笔记本<---->父类(子类品牌:Acer,Lenove,Dell...)
只需提供给用户笔记本的父类,就可以随意定制其子类的笔记本。
先定义工厂父类与各笔记本子类
public abstract class NoteBook
public abstract void Say();
public class IBM : NoteBook
public override void Say()
Console.WriteLine("我是IBM笔记本");
public class Lenovo : NoteBook
public override void Say()
Console.WriteLine("我是联想笔记");
public class Dell : NoteBook
public override void Say()
Console.WriteLine("我是Dell笔记本");
再在主程序中,调用父类制作具体的子类笔记本。
private static void Main(string[] args)
Console.WriteLine("请入输入要制作的品牌笔记本:");
string brand = Console.ReadLine();
NoteBook n = GetNB(brand);
n.Say();
Console.ReadKey();
//核心部分
public static NoteBook GetNB(string brand)
NoteBook nb;
switch (brand)
case "Dell": nb = new Dell(); break;
case "Lenovo": nb = new Lenovo(); break;
default: nb = new IBM(); break;
return nb;
三、值类型与引用类型
值类型:int,double,decimal,char,bool,enum,struct.(存储栈上)
引用类型:string,数组,自定义类,集合,接口.(存储堆上)
值传递与引用传递
值类型在赋值的时候,传递是值的本身(复制了原来的一份)
引用类型赋值时,只是传递了这个对象的引用(原对象未复制),即复制的是指针。
ref:将值传递改变成引用传递.
例:创建一个类
public class Person
private string _name;
public string Name get => _name; set => _name = value;
主函数中运行:
private static void Main(string[] args)
int n1 = 10, n2 = n1;
n2 = 20;
Console.WriteLine("n1:0,n2:1", n1, n2);//n1:10,n2:20
Person p1 = new Person();
p1.Name = "李四";
Person p2 = p1;
p2.Name = "张三";
Console.WriteLine("p1:0,p2:1", p1.Name, p2.Name);//p1:张三,p2:张三
TestRef(p1);
Console.WriteLine(p1.Name);//引用类型,在传参时一样指向同一地址
string s1 = "王五";
string s2 = s1;//两者一样
s2 = "邓六";//因字符串不可变性,新赋值将开辟新的空间,地址发生更改,不再同一地址.
Console.WriteLine("s1:0,s2:1", s1, s2);//s1:王五,s2:邓六
int n3 = 10;
TestInt(n3); //值传递
Console.WriteLine(n3);//10
TestIntRef(ref n3); //引用传递
Console.WriteLine(n3);//11
Console.ReadKey();
public static void TestRef(Person p)
Person p3 = p; p3.Name = "王五";
public static void TestInt(int n)
n++;
public static void TestIntRef(ref int n)
n++;
四、序列化与反序列化
序列化:将对象转化为二进制.
反序列化:将二进制转化为对象。
作用:传输数据,因为只有二进制才能被传输。
从A地将O对象传到B地:
A地O对象序列化成二进制,B地接受二进制后,反序列化成O对象。最终O对象从A到达了B。
序列化步骤:
1)对象标记:[Serializable]
2)使用FileStream流,用BinaryFormatter进行写入流为二进制.
反序列化步骤
1)使用FileStream流读取
2)使用Deserialize反序列化,并强制转为对象类型。从而获得对象数据。
例子:下面是序列化后保存到文件,反序列化:把文件读取后反序列到对象.
internal class Program
private static void Main(string[] args)
Person p = new Person Gender = 'M', Name = "Luck" ;
using (FileStream fsw = new FileStream(@"E:\\1.txt", FileMode.OpenOrCreate, FileAccess.Write))
BinaryFormatter bf = new BinaryFormatter();//开始序列化
bf.Serialize(fsw, p);//自动转p为二进制,用流fsw写入
Console.WriteLine("序列化完毕,去1.txt查看");
//反序列化
Person p2;
using (FileStream fsr = new FileStream(@"E:\\1.txt", FileMode.Open, FileAccess.Read))
BinaryFormatter bf = new BinaryFormatter();
p2 = (Person)bf.Deserialize(fsr);//返回为object须转换类型
Console.WriteLine(p2.Name);
Console.ReadKey();
[Serializable]
public class Person
private string _name;
private char _gender;
public string Name get => _name; set => _name = value;
public char Gender get => _gender; set => _gender = value;
五、部分类partial
同一命名空间不能定义两个同名的类.
但实际工作中,一个类很大,需要三个人同时来写这个类,因此出现部分类的概念。
就是每个人都可以在同一命名空间下写同名的类,但前面加prtial,表明是这个类
的一部分,三个人写的这个类都可以相互访问,因为这个同名部分的类本质是一个
类,哪怕分别是private.
当然不能有同名的字段;不能有同名的方法(除非重载)
例子:
internal class Program
private static void Main(string[] args)
Student s = new Student Id = 12, Name = "比尔" ;//部分类汇总
s.SayID();
Console.ReadKey();
public partial class Student//部分类
private string _name;
public string Name get => _name; set => _name = value;
public void Play()
public void SayID()
Console.WriteLine(this.Id); //可以使用下面部分类中的成员Id
public partial class Student//部分类
private int _id;
public int Id get => _id; set => _id = value;
//public void Play() //部分类的方法签名必须不一样
public void Talk()
Console.WriteLine(this.Name); //可以使用上面部分类中的成员Name
六、密封类sealed
密封者,不泄露,不继承。相当于断子绝孙,不再向下继承传递.
在类前标记为sealed,即为密封类,表明不再向下继承。
public sealed class Animal
public class Tiger : Animal //错误父类已密封不得继承
但是密封类可以从别人处继承而来。
public class Life
public sealed class Animal:Life//密封类可从别人处继承
七、重写ToString()方法
直接输出一个对象时,一般出现的就是ToString,显示的是对象的命名空间。
所有类型都可以ToString,因为都继承于Object对象中的ToString()
object中有三个虚方法:Equals(object obj)
GetHashCode()
ToString()
例子:
internal class Program
private static void Main(string[] args)
Rabbit r = new Rabbit();
Console.WriteLine(r.ToString());//Study Hard
Console.ReadKey();
public class Rabbit
public override string ToString()//重写
return "Study Hard";
八、接口简介
接口结束后,C#的基础就讲完了。
1、普通属性与自动属性
普通属性有字段与属性两者;自动属性只有属性简写,没有方法体。
但两者本质上经反编译后是相同的,普通属性不清洗时可直接用自动属性代替.
所以自动属性只能通过构造函数去限制字段。否则就写成普通属性进行限制。
public class Person
private string _name;
public string Name//普通属性
get return _name;
set _name = value;
public int Age//自动属性
get;
set;
2、接口简介
由于类的单根性,只能继承一个父类。当你想继承两个父类时,就显得尴尬了。
接口是一个规范,一个能力。
语法:
[public] interface I...able //接口名
成员;
接口中的成员不允许添加访问修饰符,默认就是public.
接口成员不能有定义。不能有方法体;同样不能有字段名,但可以有属性。
这个属性只能是自动属性,因为自动属性没有方法体。
因为接口起到桥梁作用,不起到存储数据作用。可以限定相互的规范(属性)
接口里面只能有方法、属性、索引器.本质上都是方法,即接口里都是方法。
public interface IFlyable
void Fly();
//int ID;//错误不能有字段
string Name //属性能只写成自动属性
get;
set;
string GetValue();
3、接口的特点
1)接口是一种规范,只要一个类继承了一个接口,这个类就必须实现这个接口中的所有成员。
这和抽象类一样,父类里没有方法体,子类必须重写实现。
2)为了多态,接口不能被实例化,也即:接口不能new(不能创建对象).
因为接口没有方法体,是不能进行实例创建的。不能实例化的还有静态类,抽象类。
不同的类用相同的接口,于是调用接口实现多态。
3)接口中的成员不能加访问修饰符,接口中的成员访问修饰符为public,不能修改。
接口中的成员不能有任何实现--光说不做.只是定义了一组未实现的成员。
4)接口中只能有方法、属性、索引器,事件。不能有字段和构造函数。
5)接口与接口之间可以继承,并且可以多继承。可以多接口,但类不能进行多继承。
public interface M1
void look1();
public interface M2
void look2();
public interface M3
void look3();
public interface ISuperM : M1, M2, M3
//接口继承,里面隐含look1-3
void look4();
public class Car : ISuperM
//四个实现方法缺一不可,否则出错
//void M1.look1() //出错,必须全部实现.故此方法也得实现
//
void M2.look2()
void M3.look3()
void ISuperM.look4()
6)接口并不能继承一个类,而类可以继承一个接口。
类既可以继承类,也可以继承接口。
接口能够继承接口,但不能继承类。
public class Person
public interface M1:Person//错误,接口不能继承类
void Walk1();
7)实现接口的子类必须实现该接口的全部成员。
8)一个类可以同时继承一个类并实现多个接口,如果一个子类同时继承了父类A,
并实现了接口IA,那么语法上A必须写在IA的前面。
一句话:同时继承类和接口,则类必须写在前面。
因为类是单继承,接口可以多继承,这样多个接口可以连续写在后面。
public class Person
public interface Idoable
void Walk();
public class Student : Idoable, Person//错误,接口应在类后
public void Walk()
public class Student : Person, Idoable
public void Walk()
与其说是面向对象编程,不如说是面向接口编程。
当一个抽象类实现接口的时候,需要子类去实现接口。
internal class Program
private static void Main(string[] args)
//IFlyable Ifly=new IFlyable();//错误接口不能实例化
IFlyable Ifly = new Person();//Person必须有接口
Ifly.Fly();//人类能飞吗?
Ifly = new Bird();
Ifly.Fly();//鸟儿能飞. 同样的形式不同的结果,多态
Console.ReadKey();
public class Person : IFlyable
public void Fly()
Console.WriteLine("人类能飞吗?");
public class Bird : IFlyable
public void Fly()
Console.WriteLine("鸟儿能飞");
public interface IFlyable
//接口内部只能是方法:方法、属性(自动属性)、索引器
//不允许有访问修饰符,默认为public
void Fly();
4、接口的练习
麻雀能飞,鹦鹉能飞,鸵鸟不会飞,企鹅不会飞,直升飞机会飞。
用多态来实现。需要方法、抽象类、接口
分析:1.用鸟的实体会飞方法不能解决“不会飞”的情况;
2.用鸟的会飞的抽象方法,不能通用解决不会飞的情况。
由此:是否会飞是一种能力,要用接口来实现。
同时不是所有鸟会飞,因此鸟这个父类不得有会飞接口。
鸟儿可以规定它的共性:生命、翅膀、两只脚等等。
同时,说话也是一种能力,也可以用接口。
于是:一个基类:鸟;两种接口:说话能力、飞会能力。
上面来组装题目。比如,鹦鹉用鸟父类两接口
直升机用会飞接口。
internal class Program
private static void Main(string[] args)
//接口的作用就是多态。否则分别创建各自对象,各自方法,代码繁琐混乱
IFlyable ifly = new Parrot();
ifly.Fly();//统一对象,统一方法,简单方便
ISpeak ispk = new Parrot();
ispk.Speak();
ifly = new Sparrow();
ifly.Fly();
Console.ReadKey();
public class Bird
//只能作父类,不能有会飞接口,因为有些不会飞
public void HaveWing()
Console.WriteLine("鸟有翅膀.");
public void HaveLife()
Console.WriteLine("鸟有生命.");
public interface IFlyable
//会飞的接口
void Fly();
public interface ISpeak
//说话能力不要与飞的能力混合。每种接口能力应清晰有界限
void Speak();
//因为有些不能同时具备两种能力。
public class Parrot : Bird, IFlyable, ISpeak
public void Speak()
Console.WriteLine("鹦鹉能说话.");
void IFlyable.Fly()
Console.WriteLine("鹦鹉能飞");
public class Sparrow : Bird, IFlyable
public void Fly()
Console.WriteLine("麻雀能飞.");
public class Ostrich : Bird//没啥能力,只归父类鸟
public class Penguin : Bird//同样没能力
public class Helicopter : IFlyable //不属鸟,只能飞能力
public void Fly()
Console.WriteLine("直升飞机能飞。");
5、显式实现接口
显式实现接口的目的:解决方法的重名问题。
未继承接口时,类中方法是属性类的。但是如果用了接口同名方法后,
这个类中的方法不再属于类,而是接口的。怎么将原来类的方法不属于
接口??
显式地声明一个接口同名方法,即类中在方法前加上接口名,指明这个
方法是属于接口,原同名的方法仍然还是类中的方法。
internal class Program
private static void Main(string[] args)
IFlyable ifly = new Bird();
ifly.Fly();//接口的鸟在飞
Bird f = new Bird();
f.Fly();//类中的鸟在飞
f = (Bird)ifly;
f.Fly();//类中的鸟在飞
Console.ReadKey();
public class Bird : IFlyable
public void Fly()//下面显式声明了Fly,本处Fly则属于类
Console.WriteLine("类中的鸟在飞");
void IFlyable.Fly()//前面不得用public,否则错误
Console.WriteLine("接口的鸟在飞"); //显式声明接口方法,属于接口
public interface IFlyable
void Fly();
说明:类中默认是private成员,可以添加访问修饰符private(一般都应添加以便显式识别)
接口中默认是public,如果显式实现接口,7.3版本禁止添加public,会提示错误。
同样在类中接口显式的实现,相当于接口的延伸,也不得添加public。
但是,如果是隐式实现,必须前面加public,否则会出错。
结论:子类中隐式实现的接口方法,属于子类;应该用public,不然接口无法调用而错误.
子类中显式实现的接口方法,属于接口;不能用修饰符,默认为public.
可以把接口当作实现接口类的最简的“父类”。IFlyable是Bird的"父类".
当调用接口的方法时,就会如同抽象类一样,被接口的“子类”同名方法重写了。
什么时候显式的去实现接口:
当继承的接口中的方法与参数一模一样的时候,要是用显式的实现接口。以便两者区分.
九、小结
什么时候用虚方法来实现多态?
什么时候用抽象类来实现多态?
什么时候用接口来实现多态?
提供的多个类,如果可以从中抽象出一个父类,并且在父类必须写上这几个子类共有的
方法(提炼出来的方法),此时,
共有的方法不知道怎么实现,只能在子类去实现,用抽象类;
共有的方法知道实现,并且还创建父类的对象进行初始化,用虚方法;
接口则是这几个类无法找出共同的父类,但是可以找出它们有共同的能力、共同的行为。
(鸟与直升机无法找出共同的父类,但可以找出共同的能力--会飞)
练习一:真的鸭子会游泳,木头鸭子不会游泳,橡皮甲子会游泳
会干什么、能干什么,表明一种能力,用接口。
尽管可以真鸭子作父类,但不能把游泳作为共同方法,因为木头不会游泳。虚方法pass.
由于真鸭子需要创建对象,不能用抽象方法。
internal class Program
private static void Main(string[] args)
ISwimable isw = new RealDuck();
isw.Swim();//真的鸭子会嘎嘎游
isw = new RubberDuck();
isw.Swim();//橡皮鸭子飘着游
Console.ReadKey();
public class RealDuck : ISwimable
public void Swim()
Console.WriteLine("真的鸭子会嘎嘎游");
public class WoodDuck
public class RubberDuck : ISwimable
public void Swim()
Console.WriteLine("橡皮鸭子飘着游");
public interface ISwimable
void Swim();
分析一:
public class RealDuck : ISwimable
public void Swim()
Console.WriteLine("真的鸭子会嘎嘎游");
public class RubberDuck : RealDuck, ISwimable
public interface ISwimable
void Swim();
橡皮鸭Rubber未实现接口方法Swim,不会报错,因为父类真实鸭RealDuck已经
实现,由父类继承到子类,子类中就有实现的Swim方法了。
父类RealDuck中的接口方法Swim,是属于RealDuck,不是ISwimable的。因为它
是隐式的,所以前面加了public.
分析二:将分析一中的RubberDuck改成下面:
public class RubberDuck : RealDuck, ISwimable
void Swim()
Console.WriteLine("橡皮同名鸭子");
类内Swim是隐式接口方法,属于RubberDuck,所以默认前面是private,由于与父类
中RealDuck中的Swim重名,当ISwimable isw = new RubberDuck()时,调用:
isw.Swim();//真的鸭子会嘎嘎游
可以看到,本类中是private不会调用,直接调用了父类中的真鸭子。
同时该Swim会警告提示,因为这个重名了,若故意隐藏父类重名应用New来表示:
private new void Swim()
尽管不会警告,仍会提示不会使用,因为是private,改成public,调用上面:
isw.Swim();//橡皮同名鸭子
这样,重载就直接调用的是本类橡皮鸭子的Swim了。
注意,这里不能因重载,前面写成public override void Swim(),这样会出错,
因为这里override只能与virtual配套使用。即前面要有虚方法。或者是抽象方法。
十、GUID(全局唯一标识符)
GUID(Globally Unique Identifier)全局唯一标识符,是一种由算法生成的二进制
长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。
在理想情况下,任何计算机和计算机集群都不会生成两个相同的GUID。GUID的总数
达到了2^128(3.4×10^38)个,所以随机生成两个相同GUID的可能性非常小,但并不为0。
所以,用于生成GUID的算法通常都加入了非随机的参数(如时间),以保证这种重复的
情况不会发生。
Guid.NewGuid() 产生一个不会重复的ID
十一、项目:超市收银系统
超市分三大块:
1)商品:有商品ID(GUID码,唯一码)、价格、上货数量
为了便于管理,建立一个商品父类,包括上面三个属性内容。
2)仓库:1存储货物;2提货;3进货。
3)收银:
第一步:建立商品类,假定商品有Acer、banana、JiangYou(酱油)、Samsung四类
其中它们的父类ProductFather.
internal class ProductFather
//商品父类
public string ID get; set;
public double Price get; set;
public string Name get; set;
public ProductFather(string id, double price, string name)
this.ID = id;
this.Price = price;
this.Name = name;
//下面四个子类
internal class Acer : ProductFather
public Acer(string id, double price, string name) : base(id, price, name)
internal class JiangYou : ProductFather
public JiangYou(string id, double price, string name) : base(id, price, name)
internal class SamSung : ProductFather
public SamSung(string id, double price, string name) : base(id, price, name)
internal class Banana : ProductFather
public Banana(string id, double price, string name) : base(id, price, name)
第二步:建立仓库类(重点)
存储货物,用数组不可取,长度不能伸缩。
下面用集合也不可取,4种就要4个集合,10000种商品就要10000个集合:(,程序员
添加商品都会写10000行....
List<SamSung> lstSam=new List<SamSung>();
List<Banana> lstBan=new List<Banana>();
List<JiangYou> lstJY=new List<JiangYou>();
List<Acer> lstAcer=new List<Acer>();
//.....
所以第一步中的商品类父类,ProductFather就很重要了,它屏蔽了各种商品的差异
性,于是就用父类来解决:
List<ProductFather> lst1 = new List<ProductFather>();
但是,这只是一种类型:父类。无法区别子类商品。仓库时货物都是分门别类,排
列整齐,存储与放置都很方便的。而不是直接用父类(无法区别子类)进行混合存放。
所以好像又是死路了。用字典键值对,一样,也是有一个商品一个数据,雷同于最
开始的10000种商品的做法,也行不通。
集合组成的集合:
List<List<ProductFather>> lst2 = new List<List<ProductFather>>();
注意:lst1是由ProductFather类型元素组成的集合,有四种类型元素;
lst2是由集合list<ProductFather>组成的。因此list2的元素是集合.
仓库由很多货架组成,每个货架摆放的是同一类型商品。
因此每一个货架是一个集合,由同一种类型的商品(元素)组成。
仓库是大集合,由每个货架(集合)作为元素来组合成仓库。题中有商品对应四个货架.
所以上面集合组成的集合,lst2[0]-lst2[3]分别对应四个货架,是四个集合,
每一种集合存储对应的一种商品元素.
一句话,现在没添加货物,只是仓库初始化。(以便后加添加货架)
因为其构造函数,可以如下写四句就是四个货架:
lst2.Add(new List<ProductFather>());
但这仍然是有多少商品写多少货架。可是,这个货架的形式一样,对上一句,就可
以用循环了,四个货架循环四次,10000个货架循环10000次。
流程:仓库用构造函数时,就会创建四个空的货架(无货),还需要添加货物。再通过方法
进货进行添加到货架。取货时返回到父类
internal class CangKu
//private List<ProductFather> lis1 = new List<ProductFather>();//与下一句有区别
private List<List<ProductFather>> lst2 = new List<List<ProductFather>>();//泛型嵌套
public CangKu()
lst2.Add(new List<ProductFather>());
lst2.Add(new List<ProductFather>());
lst2.Add(new List<ProductFather>());
lst2.Add(new List<ProductFather>());
//lst[0]Acer, lst2[1]SamSung, lst2[2]JiangYou, lst2[3]Banana
public void JinPros(string strType, int count)
//逐步将同种商品入库,生成唯一码对应每一个商品ID
for (int i = 0; i < count; i++)
switch (strType)//疑惑:同样商品10000种,分支10000种?
case "Acer":
lst2[0].Add(new Acer(Guid.NewGuid().ToString(), 1000, "宏基电脑"));
break;
case "SamSung":
lst2[1].Add(new SamSung(Guid.NewGuid().ToString(), 1000, "三星手机"));
break;
case "JiangYou":
lst2[2].Add(new JiangYou(Guid.NewGuid().ToString(), 1000, "酱油路人"));
break;
case "Banana":
lst2[3].Add(new Banana(Guid.NewGuid().ToString(), 1000, "香蕉你个菠萝"));
break;
public ProductFather[] QuPros(string strType, int count)//返回父类屏蔽子类差异
//取货,取多个固定大小用数组,从集合中移除有的货物
ProductFather[] pros = new ProductFather[count];
for (int i = 0; i < count; i++)
switch (strType)
case "Acer":
if (lst2[0].Count == 0) break;//货物空,自然返回数组中元素为null
pros[i] = lst2[0][0];
lst2[0].RemoveAt(0);//移除索引0,后面自动填充索引移动,不是元素移动
break; //即原索引1变成移后索引0,后面以此类推
case "SamSung":
if (lst2[1].Count == 0) break;
pros[i] = lst2[1][0];//移除之前判断仓库是否有货,才能安全地移除
lst2[1].RemoveAt(0);//也可写成lst2[1].RemoveAt(lst2[1].Count - 1);
break;
case "JiangYou":
if (lst2[2].Count == 0) break;
pros[i] = lst2[2][0];
lst2[2].RemoveAt(0);
break;
case "Banana":
if (lst2[3].Count == 0) break;//如果一直没货,返回数组元素即为null
pros[i] = lst2[3][0];
lst2[3].RemoveAt(0);
break;
return pros;
public void ShowPros()//展示货物有哪些
foreach (var item in lst2)
Console.WriteLine("超市有0货物1个,每个2元", item[0].Name, item.Count, item[0].Price);
第三步,超市类
创建仓库(初始化实例),创建超市:进货,出货,计算金额。还有结算打折。
但是打折有很多种,无法固定,因此需要有一个父类来完善。父类无需知道打折的
实现,根据输入的选择,由子类来具体实现打折的活动。故父类应是抽象类CalFather.
与前面的简单工厂设计模式相同:根据用户的输入返回一个父类对象(GetCal)
在未创建仓库时,仓库中是无货物,只有创建超市时里面创建了仓库,加入了货物,
此时就可以显示ShowPors(),该方法内部去调用ck.ShowPros();
internal class SupperMarket
private CangKu ck = new CangKu();
public SupperMarket()
//必须与进化中的CangKu类中JinPros()中货物字串相匹配,不然无法进入货架
ck.JinPros("Acer", 1000);
ck.JinPros("SamSung", 1000);
ck.JinPros("JiangYou", 1000);
ck.JinPros("Banana", 1000);
public void AskBuying()//询问并取货
Console.WriteLine("欢迎光临,请问您需要什么?");
Console.WriteLine("我们有Acer,SamSung,Jianyou,Banana:");
string strTypte = Console.ReadLine();
Console.WriteLine("您需要多少?");
int count = Convert.ToInt32(Console.ReadLine());
//取货
ProductFather[] pros = ck.QuPros(strTypte, count);
double realmoney = GetMoney(pros);//总金额
Console.WriteLine("你总共应付0元", realmoney);
//打折
Console.WriteLine("选择打折方式:1.不打折,2.打九折,3.打85折,4.买300送50,5.买500送100");
string input = Console.ReadLine();
//通过简单工厂的设计模式。根据用户输入获得一个打折对象
CalFather cal = GetCal(input);//返回父类.
//调用父类方法。实则在多态在子类中重写。
double totalMoney = cal.GetTotalMoney(realmoney);
Console.WriteLine("打折后应付0元.", totalMoney);
Console.WriteLine("你的购物清单:");
foreach (var item in pros)
Console.WriteLine("货物编号:0,\\t货物名称:1,\\t货物人价格:2", item.ID, item.Name, item.Price);
public CalFather GetCal(string input)//简单工厂类
CalFather cal = null;
switch (input)
case "1": cal = new CalNormal(); break;
case "2": cal = new CalRate(0.9); break;
case "3": cal = new CalRate(0.85); break;
case "4": cal = new CalMN(300, 50); break;
case "5": cal = new CalMN(500, 100); break;
return cal;
public double GetMoney(ProductFather[] pros)//计算金额
double realmoney = 0;
//第一种:有bug
//for (int i = 0; i < pros.Length; i++)
//
// realmoney += pros[i].Price;//可改成pros[0]
//
//第二种:也有bug
//realmoney = pros[0].Price * pros.Length;
//第三种:因为返回的父类数组pros[]可能不是满的,甚至全部元素为空。
int i = 0;
while (pros[i] != null)
realmoney += pros[i].Price;//考虑到以后可能不同货物,用i而不用0
if (++i > pros.Length - 1) break;
return realmoney;
public void ShowPros()
ck.ShowPros();
超市类中要用到简单工作设计模式的打折写法。
由父类CalFather抽象方法,到三个子类CalNormal,CalRate,CalNM去实现:
internal abstract class CalFather
/// <summary>
/// 计算打折后的金额
/// </summary>
/// <param name="realmoney">折前金额</param>
/// <returns>折后金额</returns>
public abstract double GetTotalMoney(double realmoney);
internal class CalNormal : CalFather
public override double GetTotalMoney(double realmoney)
return realmoney;
internal class CalMN : CalFather
//买M送N
public double M get; set;
public double N get; set;
public CalMN(double m, double n)
this.M = m; this.N = n;
public override double GetTotalMoney(double realMoney)
if (realMoney >= this.M)
return realMoney - (int)(realMoney / this.M) * this.N; //几个M送几N
else
return realMoney;
internal class CalRate : CalFather
public double Rate
get; set;
public CalRate(double rate)//创建时就取得折率
this.Rate = rate;
public override double GetTotalMoney(double realmoney)
//需提前知道折率
return realmoney * this.Rate;
第四步,在主函数操作
创建超市对象(内含货物进入),然后显示一下货物。
然后与用户交互:进行商品交易,金额打折,以及小票显示。
internal class Program
private static void Main(string[] args)
SupperMarket sm = new SupperMarket();
sm.ShowPros();
sm.AskBuying();
sm.ShowPros();
Console.ReadKey();
程序运行操作显示:
超市有宏基电脑货物1000个,每个1000元
超市有三星手机货物1000个,每个1000元
超市有酱油路人货物1000个,每个1000元
超市有香蕉你个菠萝货物1000个,每个1000元
欢迎光临,请问您需要什么?
我们有Acer,SamSung,Jianyou,Banana:
Acer
您需要多少?
11
你总共应付11000元
选择打折方式:1.不打折,2.打九折,3.打85折,4.买300送50,5.买500送100
3
打折后应付9350元.
你的购物清单:
货物编号:ef76a5c7-29c3-45b7-8938-d8aa8a8896df, 货物名称:宏基电脑, 货物人价格:1000
货物编号:72128793-908c-496d-9195-50e228bffe27, 货物名称:宏基电脑, 货物人价格:1000
货物编号:faec6c84-c3e7-4830-8714-2148f9156602, 货物名称:宏基电脑, 货物人价格:1000
货物编号:7483ad5e-78d2-4f05-a2ed-99319bf9c3d9, 货物名称:宏基电脑, 货物人价格:1000
货物编号:a1908cf1-5763-4e42-9d88-425dd10d15cf, 货物名称:宏基电脑, 货物人价格:1000
货物编号:7a0497fb-9bff-450b-aa14-5da0ad27b4c0, 货物名称:宏基电脑, 货物人价格:1000
货物编号:47e3cf90-a072-4a16-8f3a-a1387c50aac2, 货物名称:宏基电脑, 货物人价格:1000
货物编号:2b0c57fe-0732-4fe1-a182-c8ba302bf578, 货物名称:宏基电脑, 货物人价格:1000
货物编号:6cd32184-26e8-4598-b3cf-8f724e8b6153, 货物名称:宏基电脑, 货物人价格:1000
货物编号:c8cc38a7-fd17-49a4-ac68-14b83f961620, 货物名称:宏基电脑, 货物人价格:1000
货物编号:b2cc1f9c-94ac-47d5-abb7-aaa871d4e9aa, 货物名称:宏基电脑, 货物人价格:1000
超市有宏基电脑货物989个,每个1000元
超市有三星手机货物1000个,每个1000元
超市有酱油路人货物1000个,每个1000元
超市有香蕉你个菠萝货物1000个,每个1000元
以上是关于(13)C#传智:访问修饰符,简单工厂模式,序列化与反序列化,部分类,密封类,接口(第13天)的主要内容,如果未能解决你的问题,请参考以下文章