C# 基礎知識系列- 4 面向對象


面向對象

面向對象是一個抽象的概念,其本質就是對事物以抽象的方式建立對應的模型。
簡單來講,比如我有一只鋼筆,那么我就可以通過分析,可以得到 這只鋼筆的材第是塑料,品牌是個雜牌 ,里面裝的墨是黑色的,可以用。這時候就能建立一個鋼筆的模型,它在這里應該有這些屬性:

file

圖是一個不正確的UML類圖,但是可以簡單的概述一下我們抽象的結果。這個圖就描述了一個我們抽象出來的鋼筆類應該有哪些特性,而我手里的那只鋼筆就可以看做是鋼筆類的一個實例。
簡單來講,面向對象編程就是針對一個事件或者說一個過程,找到這個過程中參與的所有人、事務或者相對獨立的個體,按照他們在這個過程中表現,提取出他們的特性,描述他們的行為,然后按照類別不同再抽象出類來。
所以,類是事物的概念抽象,事物是類的特殊實例。

創建一個類

上面簡單的介紹了面向對象的概念,現在先創建一個C#類,然后介紹一下這個類需要注意的地方:

public class Person
{
	private static int count;

	public static int Count
	{
		get { return count; }
		set { count = value; }
	}

	private string name;

	public string Name
	{
		get { return name; }
		set { name = value; }
	}
	public Person()
	{
		Name = "";
		Count = Count + 1;
	}

	public Person(string name)
	{
		this.Name = name;
	}

	public void SayHello()
	{
	}
}

其中:
private string name;private static int count;這兩個在C#中稱為Field,也就是字段的意思;
public static int Countpublic string Name 這兩個在C#中稱為Property,也就是屬性的意思。
當然,不是說一個是private就叫字段,另一個是public就稱之為屬性,而是因為屬性有getset來控制訪問和賦值的行為。

public Person()public Person(string name)是構造方法,所謂的構造方法就是初始化一個實例的方法,調用形式如下:
Person p = new Person() 通過new關鍵字+類名+對應的參數列表即可。構造方法沒有返回類型,方法名必須是類名,可以有任意個參數。

面向對象的三大特性

面向對象的三大特性是封裝、繼承、多態。我把它們稱為面向對象面試三巨頭,因為一旦面試,如果有面向對象的問題絕對會問到這三個特性。這里先簡單介紹一下三大特性,

  • 封裝:對象的方法實現對外是隱藏的,就像我們在不拆開鋼筆之前很難知道鋼筆的墨水是怎么流動然后寫出字的;
  • 繼承:子類天然擁有父類的屬性和方法,假如我們還有一只特種鋼筆,那么我們可以把這只特種鋼筆抽象出的類認為是鋼筆的子類,這只特種鋼筆跟鋼筆一樣,可以用來做鋼筆能做的事,雖然有時候不好用;
  • 多態:簡單來講就是多種狀態,對於面向對象來說,就是方法重寫和方法重載。比如說,我們去找領導簽字,領導在忙讓我們把文件放那邊,過一會領導派人送過來簽好字的文件。如果領導有多只鋼筆,那么領導用哪只筆、在什么時候、用什么姿勢對於我們來說就是不確定的狀態,這就是多態的一種。

訪問控制符

在將三大特性之前,先介紹一下 C#的訪問控制。C#常用的訪問控制分為四種:

  • private: 限定只有同屬於一個類的成員才可以訪問,如果限定一個類是私有類,那么這個類必須是內部類
  • protected: 限定當前類的成員、子類可以訪問,不能用來限定外部類,同private一樣,如果限定類是受保護類,這個類必須是內部類
  • internal(default):默認訪問權限,對於類和方法來說,限定同一個DLL可以訪問,其他DLL不能訪問。區別是類的 internal 關鍵字可以省略,方法如果省略訪問權限符,則默認是protected
  • public:公開,所有能引用類的地方都能訪問類里的public對象,這是最開放的權限。

C#還有更多的訪問控制,不過常用的只有這四種,更多的可以參照【官方文檔】。

封裝

封裝簡單來講就是調用方不知道被調用方的具體實現以及內部機制,就像我們看別人只能看到外表缺看不到器官的運作(當然除非你是醫生)。
那么封裝有什么好處呢:

  • 對外隱藏實現,防止外部篡改引發安全問題
  • 減少不必要的關聯,被調用方需要調用方提供參數,但除此之外調用方只需要靜待被調用方返回結果就行
  • 打包一系列的操作,防止中間發生變故

比如說一個鍾表,給我們一堆零件,在沒有拼接、安裝好之前也就是封裝好,這個鍾表是不能正常使用的。只有我們按照一定邏輯,將零件安裝好之后(封裝),再裝上電池或上發條(調用) 鍾表才會轉起來。
簡單的用代碼介紹一下:

class Program
{
	static void Main(string[] args)
	{
		Person p = new Person();
		p.SayHello();
	}
}


public class Person
{
	private static int count;

	public static int Count
	{
		get { return count; }
		set { count = value; }
	}

	private string name;

	public string Name
	{
		get { return name; }
		set { name = value; }
	}

	public Person()
	{
		Name = "小明";
		Count = Count + 1;
	}

	public Person(string name)
	{
	}

	public void SayHello()
	{
		Console.WriteLine("你好,我叫"+Name);
	}
}

簡單看一下,對於Program類來講,Person的SayHello是怎么實現的完全不知情。這就是封裝的意義。

繼承

C#的繼承是單繼承,也就是說一個類只有一個父類,如果不指明父類,那么它的父類就是object。換句話說,object是C#繼承體系里的根,也就是所有類的祖先類。
C#的繼承用 表示,即 class B: A表示B繼承A。

public class A
{
	public virtual void Say()
	{
	}
	public void Look()
	{
	}
}
public class B : A
{
	public override void Say()
	{
	}
}

上述代碼建立了一個簡單的繼承體系。那么問題來了,繼承有什么用呢?簡單來講,對於A和B在Say方法有不同的實現,對於調用方來講,它們的表現應當是一致的。換句話說,就是所有用到A的地方,都能用B來代替,這不會出現任何問題。
繼承可以簡化很多行為(方法)一致的寫法。如示例所述,B類在Look上與其父類A類有着一致的行為和表現,那么B就可以省略了Look的定義和描述,沿用父類的方法。通過繼承可以很快地建立一套豐富的方法體系。子類每一次對父類的方法補充都會在子類里體現出來。所以繼承可以說是面向對象的核心體系。

有個關鍵字需要額外的講解一下saled,如果看到一個類有這個標記,那么需要注意了,這個類是不可被繼承的類。

多態

多態的實現就是通過類的繼承和方法的重載實現的。類的繼承主要通過重寫父類方法或者覆蓋父類方法來實現的,主要關鍵字就是 virtualoverridenew
具體的介紹是:

  • virtual 關鍵字聲明函數為虛函數,意思就是子類可能會重寫該方法
  • override 用在子類,用來聲明該方法是重寫父類的方法
  • new 跟實例化對象的new不同,這個放在方法前的意思是該方法會隱藏父類方法的實現。
public class A
{
	public virtual void Say()
	{
		//省略實現
	}

	public void SetName()
	{
		//省略實現
	}
}

public sealed class B:A
{
	public override void Say() //重寫父類方法
	{
		//省略實現
	}
	public new void SetName() // 覆蓋父類方法
	{
		//省略實現
	}
}

重寫和覆蓋的區別在哪呢:

A a = new B();
a.Say();// 調用的是 B中 Say方法
a.SetName();//調用的是A的SetName 方法
B b = (B)a;
b.SetName();//調用的是B的SetName 方法
b.Say();// 調用的是 B中 Say方法

類和接口

C#中類和接口的聲明方式不同,類用的關鍵字是class,接口用的是interface。而且類是繼承,接口是實現,一個類只能有一個父類,接口可以有多個。
接口需要注意的地方就死,接口所有的方法都是public的,因為接口就是用來定義規范的,所以一旦它的方法訪問控制不是public的話,就沒什么意義。

public class Demo1
{
}
public interface IDemo
{
	string Method();
}
public class Demo3 : Demo1, IDemo
{
	public string Method()
	{
		return "test";
	}
	string IDemo.Method()
	{
		return "test2";
	}
}

接口的實現和類的繼承都是 : ,先繼承后實現。

觀察示例代碼,發現Demo3有兩個方法public string Method()string IDemo.Method() 。這兩個都是實現接口的方法,不同的地方是它們的使用:

IDemo idemo = new Demo3();
idemo.Method();//返回 test2
Demo3 demo = new Demo3();
demo.Method();// 返回 test

使用接口名.方法名實現方法的時候,這個方法對於實現類構造的對象來說是不可訪問的。當然兩種方法可以共存,但是不會兩個方法都被認為是接口的實現方法。接口優先使用接口名.方法名 作為實現方法,如果沒找到則認為同名同參的方法為實現方法。

Object 類 常用方法

object 作為基類定義了四個基本方法,這四個方法是所有子類都有的方法,也是一個核心方法:

  • Equals(object obj) 這是一個很重要的方法,它是 C#中判斷兩個對象是否相等的依據,也就是 == 運算符的結果,如果不重寫這個方法的話,返回的結果是兩個對象是否指向同一個引用地址。
  • GetType() 返回這個對象的類型,這是反射機制中重要的一塊
  • ToString() 返回字符串,獲得一個對象的文字描述,默認返回的是對象的地址描述信息,這個方法建議重寫
  • GetHashCode() 返回 Hash值,某些集合和程序機制會以HashCode作為元素的相等性判斷依據,所以在重寫 Equals 之后也要重寫 這個方法,並保證兩個方法對於相同的對象做相等性結果判定是應該表現一致。

擴展方法

C# 有一個很重要的機制就是擴展方法,擴展方法表現出的跟類自有的方法調用結果一致。
具體寫法如下:

public static class Methods
{
	public static string Test(this Person person)
	{
			return "test";
	}
}

需要注意的是,擴展方法所在類必須是靜態類,擴展方法必須是靜態方法,擴展方法第一個參數就是擴展的元素對象,用this標記。

不過很多人對擴展方法褒貶不一,有人認為擴展方法極易破壞繼承鏈,導致一些不必要的麻煩;有人認為擴展方法就跟工具方法一樣,而且可以優化調用方式,統一使用風格。

不過我看來,擴展方法利大於弊。因為擴展方法可以在不修改原有類的基礎上增加功能,同時它也是一個工具類,跟普通的方法是一致的。

更多內容煩請關注我的博客

file


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM