我们上一部分介绍了运算符,选择语句和循环语句、数组、类型转换,接下来开始编写我们自己的函数
编程的一条基本原则是“不要重复自己(DRY)”
编程的一条基本原则是“不要重复自己(DRY)”
编程的一条基本原则是“不要重复自己(DRY)”
一、面向对象编程
在C#中,可以使用C#关键字class(大多时候)和struct(偶尔)来定义对象的类型。
··封装是与对象相关的数据和操作的组合,将数据和函数等集合在一个个的单元(类)中,它是实现面向对象程序设计的第一步,想要访问对象的数据只能通过已经定义的接口,被封装的对象我们通常称为抽象数据类型,适当的封装可以提高安全性,通常我们通过private,protected,public来实现封装
··组合是指物体由什么构成
··聚合是指什么可以与对象相结合,通过聚合两个独立的对象,可以形成新的组件
··继承就是将公用的属性或方法抽离父类的过程
1:面向对象的设计原则
①单一职责原则(SRP):
(1)、SRP(Single Responsibilities Principle)的定义:就一个类而言,应该仅有一个引起它变化的原因。简而言之,就是功能要单一。
(2)、如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其它职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。(敏捷软件开发)
(3)、软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。
小结:单一职责原则(SRP)可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。责任过多,引起它变化的原因就越多,这样就会导致职责依赖,大大损伤其内聚性和耦合度。
②开放关闭原则(OCP)
(1)、OCP(Open-Close Principle)的定义:就是说软件实体(类,方法等等)应该可以扩展(扩展可以理解为增加),但是不能在原来的方法或者类上修改,也可以这样说,对增加代码开放,对修改代码关闭。
(2)、OCP的两个特征: 对于扩展(增加)是开放的,因为它不影响原来的,这是新增加的。对于修改是封闭的,如果总是修改,逻辑会越来越复杂。
小结:开放封闭原则(OCP)是面向对象设计的核心思想。遵循这个原则可以为我们面向对象的设计带来巨大的好处:可维护(维护成本小,做管理简单,影响最小)、可扩展(有新需求,增加就好)、可复用(不耦合,可以使用以前代码)、灵活性好(维护方便、简单)。开发人员应该仅对程序中出现频繁变化的那些部分做出抽象,但是不能过激,对应用程序中的每个部分都刻意地进行抽象同样也不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。
③里氏代替原则(LSP)
(1)、LSP(Liskov Substitution Principle)的定义:子类型必须能够替换掉它们的父类型。更直白的说,LSP是实现面向接口编程的基础。
小结:任何基类可以出现的地方,子类一定可以出现,所以我们可以实现面向接口编程。 LSP是继承复用的基石,只有当子类可以替换掉基类,软件的功能不受到影响时,基类才能真正被复用,而子类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
④依赖倒置原则(DIP)
(1)、DIP(Dependence Inversion Principle)的定义:抽象不应该依赖细节,细节应该依赖于抽象。简单说就是,我们要针对接口编程,而不要针对实现编程。
(2)、高层模块不应该依赖低层模块,两个都应该依赖抽象,因为抽象是稳定的。抽象不应该依赖具体(细节),具体(细节)应该依赖抽象。
小结:依赖倒置原则其实可以说是面向对象设计的标志,如果在我们编码的时候考虑的是面向接口编程,而不是简单的功能实现,体现了抽象的稳定性,只有这样才符合面向对象的设计。
⑤接口隔离原则(ISP)
(1)、接口隔离原则(Interface Segregation Principle, ISP)指的是使用多个专门的接口比使用单一的总接口要好。也就是说不要让一个单一的接口承担过多的职责,而应把每个职责分离到多个专门的接口中,进行接口分离。过于臃肿的接口是对接口的一种污染。
(2)、使用多个专门的接口比使用单一的总接口要好。
(3)、一个类对另外一个类的依赖性应当是建立在最小的接口上的。
(4)、一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。
(5)、“不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。”这个说得很明白了,再通俗点说,不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。
小结:接口隔离原则(ISP)告诉我们,在做接口设计的时候,要尽量设计的接口功能单一,功能单一,使它变化的因素就少,这样就更稳定,其实这体现了高内聚,低耦合的原则,这样做也避免接口的污染。
⑥组合复用原则(CRP)
(1)、组合复用原则(Composite Reuse Principle, CRP)就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分。新对象通过向这些对象的委派达到复用已用功能的目的。简单地说,就是要尽量使用合成/聚合,尽量不要使用继承。
(2)、要使用好组合复用原则,首先需要区分”Has—A”和“Is—A”的关系。 “Is—A”是指一个类是另一个类的“一种”,是属于的关系,而“Has—A”则不同,它表示某一个角色具有某一项责任。导致错误的使用继承而不是聚合的常见的原因是错误地把“Has—A”当成“Is—A”.例如:鸡是动物,这就是“Is-A”的表现,某人有一个手枪,People类型里面包含一个Gun类型,这就是“Has-A”的表现。
小结:组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏替换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
⑦迪米特法则(Law of Demeter)
(1)、迪米特法则(Law of Demeter,LoD)又叫最少知识原则(Least Knowledge Principle,LKP),指的是一个对象应当对其他对象有尽可能少的了解。也就是说,一个模块或对象应尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立,这样当一个模块修改时,影响的模块就会越少,扩展起来更加容易。
(2)、关于迪米特法则其他的一些表述有:只与你直接的朋友们通信;不要跟“陌生人”说话。
(3)、外观模式(Facade Pattern)和中介者模式(Mediator Pattern)就使用了迪米特法则。
小结:迪米特法则的初衷是降低类之间的耦合,实现类型之间的高内聚,低耦合,这样可以解耦。但是凡事都有度,过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。
2:类(class)
其实关于类我们之前有简单的介绍过,类是一种抽象的概念,它其实是具有相同特性(数据元素)和行为(功能)的对象们的抽象。可以说类的实例是对象,类实际上就是一种数据类型。
①创建声明类
public class ClassName(假设不指定继承,类将从System.Object继承)
*在C#中,关键字pulic位于class之前,public叫做访问修饰符,public访问修饰符表示允许所有其他代码访问这个ClassName类。创建新的类意味着在当前项目中产生了一种新的数据类型。
②创建类的成员
成员可以是字段、方法或它们二者的特定版本
字段:有常量字段、只读字段、事件
常量字段:数据永远不变,编译器会将数据复制到读取它们的任何代码中
只读字段:在类实例化后,数据不能改变
事件:数据引用一个或多个方法,方法在发生事件时执行(详情见后续)
方法:用于执行语句,有构造函数、属性、索引器、运算符
构造函数:使用new关键字分配内存和实例化类时执行的语句。
属性:获取或设置数据时执行的语句,属性是封装字段的首选方法,除非需要公开字段的内存地址
索引器:使用数组语法[]获取或设置数据时执行的语句
运算符:对类型的操作数进行运算符执行
第一种:无static的访问方式(变量为普通变量)
public class MyClass //>>>>>>>>>创建了类,声明了变量和函数
{
public int a=10; //创建了int普通成员
public void Func1() //创建了func1普通成员函数
{
Debug.Log("Func1 Called");
}
}
第二种:有static的访问方式(静态变量)
*static声明的类内的变量,称为类的静态成员变量
*static声明的类内的函数,成为类的静态成员函数
public class MyClass
{
public static int a=10; //静态成员变量
public static void Func1() //静态成员函数
{
Debug.Log("Func1 Called");
}
}
③实例化类
用类的定义来创建一个实例,就叫做类的实例化。
实例化的具体方法:类 对象= new 类()
public class MyClass
{
public int a=10;
public void Func1()
{
Debug.Log("Func1 Called");
}
}
public class StudyBaseClass : MonoBehaviour
{
void Start()
{
MyClass myClass=new MyClass(); //使用了new进行类的实例化,
}
④类的访问
在类的内部声明的变量或函数,若想访问,必须通过类名进行访问
无static时访问如下:可以直接访问实例化后的类名
int b=myClass.a;
有static时访问如下:必须访问实例化之前的类名
int b=MyClass.a;
3:类---字段中存储数据
①:定义字段
假设一个人的信息由姓名和出生日期组成,在Person类内部封装这两个值,他们在类外可见,可以对字段使用任何类型,包括数组和集合等。
public class Person: Object
{
public string Name;
public DateTime DateOfBirth;
}
②:理解访问修饰符
封装的一部分时选择成员的可见性。
访问修饰符有4个,并且有两种组合可以应用到类内成员
private | 成员只能在类内访问,这是默认设置 |
internal | 成员可在类型内部或者同一程序集的任何类型中访问 |
protected | 成员可以在类型内部或从类型继承的任意类型中访问 |
public | 成员可在任何地方进行访问 |
internal protected | 成员可在类型内部或者同一程序集、从类型继承的任何类型中访问,相当于internal_or_protected |
private protected | 成员可在类型内部或者同一程序集、从类型继承的任何类型中访问,相当于internal_and_protected |
4:与类同名的构造函数和析构函数
Using system;
nameSpace LineApplication
{
Public class Line
{
Private double length; //线条的长度
Public Line()// 构造函数写法,与类名同名;
~Line()// 析构函数写法,与类名同名,前加~;
}
}
类名为Line
构造函数为Public Line():构造函数分为有参数和无参数的,没有任何返回值类型的声明。发生在new实例时候,会被自动执行。可以携带参数,当你显示写了自己的构造函数后,系统将不再为你提供默认的无参构造函数了。
析构函数为~Line():析构函数没有参数也没有任何返回类型的声明,析构函数在对象销毁时自动被调用
作用域的问题:函数内声明的变量(包括类对象),在执行完本函数时,会被自动销毁,但是,在类内声明的变量(包括类对象),只有当前类被销毁时,它管理的其他变量(包括类对象)才会被销毁。
5:继承
继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得容易,已有的类称之为基类(父类),新类为派生类(子类),C#不支持多重继承。
public class Polygon
{
public int Length;
public int Width;
public string color;
public string name;
}
class Rectangle:Polygon
{
public Rectangle()
{
}
~Rectangle()
{
}
}
class Triangle:Polygon
{
public Triangle()
{
}
~Triangle()
{
}
}
void Start()
{
Rectangle recTangle=new Rectangle();//实例化子类
recTangle.Length=3;
recTangle.Width=3;
recTangle.name="矩形";
recTangle.color="红色";
Debug.Log(recTangle.color+"多边形"+recTangle.name+",长"+recTangle.Length+",宽"+recTangle.Width);
三角形代码同上
}
6:封装
隐藏技术细节,通过接口的形式暴露给第三方,而不需要关心细节,有两种封装的方法,分别是
枚举enum进行封装,覆盖override和virtual。
①:枚举以及枚举封装
(1)枚举:enum类型是一种非常有效的方式,可以存储一个或多个选项,因为在内部,enum类型结合了整数值和使用字符串描述的查找表。
每一个枚举元素都是有枚举值,默认情况下,第一个枚举值为0,后面的枚举值依次递增,可以修改值,后面的枚举数的值依次递增。(第一个写100,第二个即为101)
枚举元素默认为Int,准许使用的枚举类型有byte,sbyte,short,ushort,int,long,ulong
enum Name :long //(不修改默认为int,加:可修改)
{
Up=0,
Down=1,
Left=1,
Right=1,
}
具体的实现如下:
public class EnumTest : MonoBehaviour
{
public enum emAction
{
None=0,
Getup,//0+1=1
Wash,//1+1=2
Eat,//2+1=3
Play//3+1=4
}
//定义成员变量
public emAction mAction=emAction.Play;
void Start()
{
Func1();
}
void Update()
{
}
void Func1()
{
Debug.Log(mAction);
(2):枚举和int的互相转换
枚举→int
int iPlay=(int)mAction;
Debug.Log("Enum-->int:"+iPlay);
int→枚举(两种方式)
mAction=(emAction)3;
Debug.Log("int-->Enum:"+mAction);
mAction=(emAction)Enum.ToObject(typeof(emAction),1);
Debug.Log("int-->Enum"+mAction);
(3)枚举和string的互相转换
枚举→string
mAction.ToString()
string→枚举
mAction=(emAction)Enum.Parse(typeof(emAction),"Wash" );
Debug.Log( "string-->Enum"+mAction );
(4)Switch+枚举,实现特定显示
switch (mAction)
{
case emAction.Getup:
Debug.Log("1.起床");
break;
case emAction.Wash:
Debug.Log("2.洗漱");
break;
case emAction.Eat:
Debug.Log("3.吃饭");
break;
case emAction.Play:;
Debug.Log("玩");
break;
default:
Debug.Log("不知道做什么");
break;
}
(5):利用进行枚举封装
public enum POLYGON
{
Rectangle,
Triangle,
}
public class Polygon
{
public int Length;
public int Width;
public string color;
public string name;
public float Area;
public POLYGON emPolygon;//成员变量,因为枚举也是数据类型,类似int a,后面是名字
public void ShowBaseInfo()
{
Debug.Log(color+"多边形"+name+",长"+Length+",宽"+Width);//
}
public void CalcArea()
{
if (emPolygon==POLYGON.Rectangle)
{
Area=Length*Width;
}
else if(emPolygon==POLYGON.Triangle)
{
Area=Length*Width/2;
}
}
}
class Rectangle:Polygon
{
public Rectangle()
{
}
~Rectangle()
{
}
}
class Triangle:Polygon
{
public Triangle()
{
}
~Triangle()
{
}
}
public class inhe: MonoBehaviour
{
void Start()
{
Rectangle recTangle=new Rectangle();
recTangle.emPolygon=POLYGON.Rectangle;
recTangle.Length=3;
recTangle.Width=4;
recTangle.name="矩形";
recTangle.color="红色";
recTangle.ShowBaseInfo();
recTangle.CalcArea();
Debug.Log("矩形的面积"+recTangle.Area);
Triangle triangle=new Triangle();
triangle.emPolygon=POLYGON.Triangle;
triangle.Length=3;
triangle.Width=4;
triangle.name="三角形";
triangle.color="黄色";
triangle.ShowBaseInfo();
triangle.CalcArea();
Debug.Log("三角形的面积"+triangle.Area);
}
(6)利用override以及virtual进行封装 *
virtual代表虚函数,意味着子类可以覆盖(覆写)其实现;如果子类不覆盖,将使用父类的同名函数
public enum POLYGON
{
Rectangle,
Triangle,
}
public class Polygon
{
public int Length;
public int Width;
public string color;
public string name;
public float Area;
public POLYGON emPolygon;
public void ShowBaseInfo()
{
Debug.Log(color+"多边形"+name+",长"+Length+",宽"+Width);//
}
public virtual void CalcArea()
{
if(emPolygon==POLYGON.Rectangle)
{
Area=Length*Width;
}
else if (emPolygon==POLYGON.Triangle)
{
Area=Length*Width/2;
}
}
}
class Rectangle:Polygon
{
public Rectangle()
{
}
~Rectangle()
{
}
public override void CalcArea()//override代表覆盖
{
Area=Length*Width;
}
}
class Triangle:Polygon
{
public Triangle()
{
}
~Triangle()
{
}
public override void CalcArea()
{
Area=Length*Width/2;
}
}
public class inhe: MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Rectangle recTangle=new Rectangle();
recTangle.emPolygon=POLYGON.Rectangle;
recTangle.Length=3;
recTangle.Width=4;
recTangle.name="矩形";
recTangle.color="红色";
recTangle.ShowBaseInfo();
recTangle.CalcArea();
Debug.Log("矩形的面积"+recTangle.Area);
Triangle triangle=new Triangle();
triangle.emPolygon=POLYGON.Triangle;
triangle.Length=3;
triangle.Width=4;
triangle.name="三角形";
triangle.color="黄色";
triangle.ShowBaseInfo();
triangle.CalcArea();
Debug.Log("三角形的面积"+triangle.Area);
}
7:多态
表示同一个接口,使用不同的实例而执行不同操作。通俗来说,就是多种形态,当不同的对象去完成时会产生出不同的状态。
静态多态(编译时):在编译时,函数和对象的连接机制被成为早期绑定,也称静态绑定,C#提供了两种技术来实现静态多态性,分别为函数重载和运算符重载。
动态多态(运行时):在运行前无法确认哪个方法,只有在运行时才能确定,根据实例对象,执行同一个函数的不同行为。
public enum POLYGON //创建枚举和名称,并规定枚举成员
{
Rectangle,
Triangle,
}
public class Polygon //创建多边形父类
{
public int Length; //声明成员变量长度
public int Width; //声明成员变量宽度
public string color; //声明成员变量颜色,为字符型
public string name; //声明字符型名字
public float Area; //声明浮点型面积
public POLYGON emPolygon; //声明枚举成员变量
public void ShowBaseInfo() //声明成员函数,多边形的颜色,长宽高
{
Debug.Log(color+"多边形"+name+",长"+Length+",宽"+Width);//
}
public virtual void CalcArea() //声明虚函数,多边形面积
{
}
public virtual void Show() //声明虚函数Show
{
Debug.Log("Poylgon Show");
}
}
class Rectangle:Polygon //子类继承
{
public Rectangle() //声明构造函数
{
}
~Rectangle() //声明析构函数
{
}
public override void CalcArea() //重写面积方法
{
Area=Length*Width;
}
public override void Show() //重写show方法
{
Debug.Log("Rectangle Show");
}
}
class Triangle:Polygon //同上
{
public Triangle()
{
}
~Triangle()
{
}
public override void CalcArea()
{
Area=Length*Width/2;
}
public override void Show()
{
Debug.Log("Triangle Show");
}
public class inhe: MonoBehaviour
{
void Start()
{
Rectangle recTangle=new Rectangle(); //对矩形实例化
recTangle.emPolygon=POLYGON.Rectangle; //对矩形枚举进行赋值
recTangle.Length=3; //矩形长度赋值
recTangle.Width=4; //矩形宽度赋值
recTangle.name="矩形"; //矩形名字赋值
recTangle.color="红色"; //矩形颜色赋值
recTangle.ShowBaseInfo(); //调用SHowBaseInfo函数
recTangle.CalcArea(); //调用面积函数,此时调用重写后的
Debug.Log("矩形的面积"+recTangle.Area);
Triangle triangle=new Triangle();
triangle.emPolygon=POLYGON.Triangle;
triangle.Length=3;
triangle.Width=4;
triangle.name="三角形";
triangle.color="黄色";
triangle.ShowBaseInfo();
triangle.CalcArea();
Debug.Log("三角形的面积"+triangle.Area);
// 父 子
// 子 父
只能子给父赋值,父不能给子赋值。
Polygon baseParent1 =recTangle; //创建父类对象。将矩形类赋值给它
Polygon baseParent2 =triangle;
baseParent1.Show();
baseParent2.Show(); //调用Show方法
8:覆盖和重载
覆盖(override):发生在 继承关系中,通过virtual和override;函数名和函数参数是一样的。
重载(overolad):发生在 任何关系中,只要保证函数名字一致,参数不一致,带参数顺序不一致,或者是参数个数不一致也是可以的,即可实现重载。
重载的主要好处就是不用为了对不同的参数类型或参数个数,而写多个函数。多个函数用同一个名字,但参数表,即参数的个数或(和)数据类型可以不同,调用的时候,虽然方法名字相同,但根据参数表可以自动调用对应的函数。
//定义了几种相同的函数名,而内部参数或顺序不同。
public void ShowFunc()
{
}
public void ShowFunc(int a)
{
}
public void ShowFunc2(float c,int a,string b)
{
}
public void ShowFunc2(float c,string b,int a)
{
}
public void ShowFunc3(int a,string b,float c)
{
}