Notas de estudo em C#
Capítulo 2 A parte básica difícil
Seção 1 Delegação
O que é delegação?
- Delegado é uma versão atualizada do ponteiro de função
- Delegar significa que há uma coisa que eu mesmo não faço, mas deixo para os outros fazerem, ou seja, faço indiretamente;
#include <studio.h>
int Add(int a, int b)
{
int result = a + b;
return result
}
int Sub(int a, int b)
{
int result = a - b;
return result
}
int main()
{
int x = 100;
int y = 200;
int z = 0;
z = Add(x, y);
printf("%d+%d=%d", x, y, z);
z = Sub(x, y);
printf("%d-%d=%d", x, y, z);
system("pause");
return 0;
}
Podemos ver a saída da seguinte forma:
>> 100+200=300
>> 100-200=-100
>> Press any key to continue ...
Neste exemplo, ela é chamada através do nome da função, que é chamada diretamente.
#include <studio.h>
typedef int (* Calc)(int a, int b); // 函数指针,并且定义为一种类型
int Add(int a, int b)
{
int result = a + b;
return result
}
int Sub(int a, int b)
{
int result = a - b;
return result
}
int main()
{
int x = 100;
int y = 200;
int z = 0;
Calc funcPoint1 = &Add;
Calc funcPoint2 = ⋐
z = funcPoint1(x, y);
printf("%d+%d=%d", x, y, z);
z = funcPoint2(x, y);
printf("%d-%d=%d", x, y, z);
system("pause");
return 0;
}
Podemos ver a saída da seguinte forma:
>> 100+200=300
>> 100-200=-100
>> Press any key to continue ...
Você pode ver que os resultados de saída são os mesmos, o que mostra que os efeitos das chamadas indiretas e diretas são os mesmos.Este é o ponteiro de função na linguagem C;
- Tudo é um endereço
-
- Uma variável (dados) é um valor armazenado em uma memória a partir de um determinado endereço;
-
- Uma função (algoritmo) é um conjunto de instruções em linguagem de máquina armazenadas em uma seção da memória a partir de um determinado endereço;
- Chamada direta e chamada indireta
-
- Chamada direta: Chama a função através do nome da função, o processador obtém diretamente o endereço da função através do nome da função e inicia a execução -> retorno;
-
- Chamada indireta: Chama uma função através de um ponteiro de função. O processador obtém o endereço da função lendo o valor armazenado no ponteiro de função e inicia a execução -> retorno;
- Não existe nenhuma entidade funcional correspondente à delegação em Java;
- A delegação é simples e prática
-
- Delegado de ação; para tipo Void
-
- Delegado de função; usado com parâmetros
Parte 1 Exemplo de ação e delegação de função
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
Action action = new Action(calculator.Report); // 注意这里没有圆括号,这里只需要方法名,而不是调用方法
calculator.Report(); // 直接调用
action.Invoke(); // 间接调用,模仿函数指针的写法
action(); // 间接调用,简洁的写法
// 参数,参数,返回类型
Func<int, int, int> func = new Func<int, int, int>(calculator.Add);
Func<int, int, int> func2 = new Func<int, int, int> (calculator.Sub);
int x = 100;
int y = 200;
int z = 0;
// 间接调用,函数指针式的写法
z = func.Invoke(x, y);
Console.WriteLine(z);
z = func2.Invoke(x, y);
Console.WriteLine(z);
// 间接调用,简洁的写法
z = func(x, y);
Console.WriteLine(z);
z = func2(x, y);
Console.WriteLine(z);
}
}
class Calculator
{
public void Report()
{
Console.WriteLine("I have 3 methods");
}
public int Add(int a, int b)
{
int result = a + b;
return result;
}
public int Sub(int a, int b)
{
int result = a - b;
return result;
}
}
}
A execução do programa acima fornecerá a seguinte saída:
I have 3 methods
I have 3 methods
I have 3 methods
300
-100
300
-100
Parte 2 Delegação Personalizada
- Como o delegado é uma classe, que é um tipo de dados e um tipo de dados de referência, o delegado pode declarar variáveis e instâncias;
- O método de declaração de um delegado não é o mesmo que o método de declaração de uma classe geral, mas é mais parecido com o método de declaração de um ponteiro de função em C/C++;
O exemplo a seguir é a declaração e o uso de um delegado personalizado;
namespace ConsoleHelloWorld
{
public delegate double Calc(double x, double y);
// delegate 是类,需要声明在名称空间体里面;
// public 是访问范围,delegate 是告诉编译器要声明一个委托
// 第一个 double 是目标方法的返回值类型
// 然后 Calc 是委托的名字
// 后面的圆括号里面是目标方法的参数列表
// 到此自定义委托类型声明完成
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
// 传递的方法的参数列表必须和声明时一样,返回类型也必须一致
Calc calc1 = new Calc(calculator.Add);
Calc calc2 = new Calc(calculator.Sub);
Calc calc3 = new Calc(calculator.Mul);
Calc calc4 = new Calc(calculator.Div);
double a = 100;
double b = 200;
double c = 0;
c = calc1.Invoke(a, b);
Console.WriteLine(c);
c = calc2.Invoke(a, b);
Console.WriteLine(c);
c = calc3.Invoke(a, b);
Console.WriteLine(c);
c = calc4.Invoke(a, b);
Console.WriteLine(c);
}
}
class Calculator
{
// 有四个方法,除了名字不同,返回值类型和参数列表都是一样的
public double Add(double x, double y)
{
return x + y;
}
public double Sub(double x, double y)
{
return x - y;
}
public double Mul(double x, double y)
{
return x * y;
}
public double Div(double x, double y)
{
return x / y;
}
}
}
Executando o código acima, você pode obter a seguinte saída:
Quando customizamos a delegação, precisamos ficar atentos a alguns pontos:
- O delegado e o método encapsulado devem permanecer "compatíveis com o tipo"
- Não o coloque no lugar errado ao declarar o delegado. O delegado é uma classe e precisa ser declarado no corpo do namespace. Se você colocá-lo no lugar errado, ele pode não ser executado ou se tornar uma classe aninhada;
Como você pode ver na imagem acima, a primeira linha é a declaração do delegado, e as quatro linhas seguintes são métodos compatíveis com ela;
Parte 3 Uso geral de delegação
No trabalho, o delegado geralmente é passado como parâmetro para outro método. A vantagem disso é que o método encapsulado pelo delegado pode ser chamado indiretamente, formando uma estrutura dinâmica de chamada de método;
- O método modelo grava um método que usa o método externo especificado para gerar resultados por meio dos parâmetros delegados passados;
-
- Equivalente à pergunta de preencher as lacunas
-
- Muitas vezes localizado no meio do código
-
- O delegado tem um valor de retorno
-
- É equivalente a escrever um método, que é um modelo. Uma parte deste modelo é incerta e outras partes são determinadas. Esta parte incerta é preenchida pelo método contido nos parâmetros do tipo delegado passado;
- O método de retorno de chamada chama o método externo especificado;
-
- Equivalente a uma linha de montagem
-
- Geralmente localizado no final do código
-
- Os delegados não têm valor de retorno e geralmente são usados para lidar com algum trabalho final;
O seguinte mostra o uso de métodos de modelo:
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
Box box1 = wrapFactory.WrapProduct(func1);
Box box2 = wrapFactory.WrapProduct(func2);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
}
}
class Product
{
public string Name {
get; set; }
}
class Box
{
public Product Product {
get; set; }
}
class WrapFactory
{
public Box WrapProduct ( Func<Product> getProduct )
{
// 模板方法
Box box = new Box();
// 执行传进来的委托所封装的方法,这就是间接调用
Product product = getProduct.Invoke(); // 获取产品,将产品装入 Box
box.Product = product;
return box;
// 写成模版方法的好处是,Product类,Box类还有WrapFactory类都不需要在修改,
// 只需要扩展产品工厂,让其产出更多的产品,不管生产哪种产品的方法,
// 只需要将该方法封装在委托类型的对象里,传给模版方法,这个模版方法一定可以将
// 产品包装成箱子返回回来,极大地实现代码的重复使用
}
}
class ProductFactory
{
public Product MakePizza()
{
Product product = new Product();
product.Name = "Pizza";
return product;
}
public Product MakeToyCar()
{
Product product = new Product();
product.Name = "Toy Cat";
return product;
}
}
}
O seguinte mostra o uso de métodos de retorno de chamada:
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
Logger logger = new Logger();
Action<Product> log = new Action<Product>(logger.Log);
Box box1 = wrapFactory.WrapProduct(func1, log);
Box box2 = wrapFactory.WrapProduct(func2, log);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
}
}
class Logger
{
public void Log(Product product)
{
// Log 以回调的形式传进模版的方法里
Console.WriteLine("Product '{0}' created at {1}. Price is {2}.", product.Name, DateTime.UtcNow, product.Price);
}
}
class Product
{
public string Name {
get; set; }
public double Price {
get; set; }
}
class Box
{
public Product Product {
get; set; }
}
class WrapFactory
{
public Box WrapProduct ( Func<Product> getProduct, Action<Product> logCallback)
{
// 模板方法
Box box = new Box();
// 执行传进来的委托所封装的方法,这就是间接调用
Product product = getProduct.Invoke(); // 获取产品,将产品装入 Box
if (product.Price >= 50)
{
logCallback(product);
}
box.Product = product;
return box;
// 写成模版方法的好处是,Product类,Box类还有WrapFactory类都不需要在修改,
// 只需要扩展产品工厂,让其产出更多的产品,不管生产哪种产品的方法,
// 只需要将该方法封装在委托类型的对象里,传给模版方法,这个模版方法一定可以将
// 产品包装成箱子返回回来,极大地实现代码的重复使用
}
}
class ProductFactory
{
public Product MakePizza()
{
Product product = new Product();
product.Name = "Pizza";
product.Price = 20;
return product;
}
public Product MakeToyCar()
{
Product product = new Product();
product.Name = "Toy Cat";
product.Price = 120;
return product;
}
}
}
Quer seja um método de modelo ou um método de retorno de chamada, um método externo é encapsulado usando parâmetros do tipo delegado e, em seguida, o método é passado para o método para chamadas indiretas. Este é o uso convencional de delegados.
A delegação pode ser muito perigosa se for mal utilizada;
- Este é um tipo de acoplamento forte no nível do método, e precisamos ser cautelosos no trabalho real;
- Reduza a legibilidade e aumente a dificuldade de depuração;
- O entrelaçamento de retornos de chamada de delegados, chamadas assíncronas e multithreading tornará o código difícil de manter e ler, o que é catastrófico;
- O uso inadequado de delegados pode causar vazamentos de memória e redução do desempenho do programa;
Parte 4 Uso avançado de delegação
Delegação multicast refere-se a uma delegação que encapsula mais de um método. Veja a seguir um exemplo:
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
Student student1 = new Student() {
Id = 1, PenColor = ConsoleColor.Yellow };
Student student2 = new Student() {
Id = 2, PenColor = ConsoleColor.Green };
Student student3 = new Student() {
Id = 3, PenColor = ConsoleColor.Red };
Action action1 = new Action(student1.DoHomework);
Action action2 = new Action(student2.DoHomework);
Action action3 = new Action(student3.DoHomework);
// 多播委托的写法:
action1 += action2; // 将 aciton2 合并到 action1
action1 += action3;
action1.Invoke();
// 多播委托的执行顺序是按照你封装方法的顺序执行的
}
}
class Student
{
public int Id {
get; set; }
public ConsoleColor PenColor {
get; set; }
public void DoHomework()
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hours.", this.Id, i);
Thread.Sleep(1000); // 线程暂停一秒钟
}
}
}
}
Chamadas assíncronas implícitas
- Chamada assíncrona: relativa à chamada síncrona,
-
- Sincronização: Depois que você terminar, continuarei com base no seu trabalho;
-
- Assíncrono: Nós dois fazemos ao mesmo tempo, ou seja, cada um faz a sua coisa;
- Comparação de chamadas síncronas e chamadas assíncronas
-
- Todo programa em execução é um processo
-
- Cada processo pode ter um ou mais threads, o primeiro thread é chamado de thread principal e os demais são threads ramificados.
-
- Ao chamar métodos no mesmo thread, este último só pode ser executado após a execução do anterior, o que é chamado de chamada síncrona;
-
- O mecanismo subjacente das chamadas assíncronas éMultithreading;
-
- As chamadas síncronas são chamadas seriais de thread único e as chamadas assíncronas são chamadas paralelas multithread;
A seguir está um exemplo de uma chamada assíncrona para uma chamada síncrona
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
Student student1 = new Student() {
Id = 1, PenColor = ConsoleColor.Yellow };
Student student2 = new Student() {
Id = 2, PenColor = ConsoleColor.Green };
Student student3 = new Student() {
Id = 3, PenColor = ConsoleColor.Red };
// 直接同步调用
student1.DoHomework();
student2.DoHomework();
student3.DoHomework();
Console.WriteLine("=============================================");
Action action1 = new Action(student1.DoHomework);
Action action2 = new Action(student2.DoHomework);
Action action3 = new Action(student3.DoHomework);
// 使用委托的隐式异步调用
action1.BeginInvoke(null, null);
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);
Console.WriteLine("=============================================");
// 使用委托的显式异步调用
Task task1 = new Task(new Action(student1.DoHomework));
Task task2 = new Task(new Action(student2.DoHomework));
Task task3 = new Task(new Action(student3.DoHomework));
task1.Start();
task2.Start();
task3.Start();
Console.WriteLine("=============================================");
// 单播委托的间接同步调用
action1.Invoke();
action2.Invoke();
action3.Invoke();
Console.WriteLine("=============================================");
// 多播委托的间接同步调用
action1 += action2;
action2 += action3;
action1();
Console.WriteLine("=============================================");
}
}
class Student
{
public int Id {
get; set; }
public ConsoleColor PenColor {
get; set; }
public void DoHomework()
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hours.", this.Id, i);
Thread.Sleep(1000); // 线程暂停一秒钟
}
}
}
}
Parte 5 Use interface quando apropriado para substituir alguns usos de delegação
O uso inadequado da delegação aumentará a dificuldade de manutenção do código.O uso de interfaces pode evitar esses problemas desnecessários e obter as mesmas funções;
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
IProductFactory pizzaFactory = new PizzaFactory();
IProductFactory toycarFactory = new ToyFactory();
WrapFactory wrapFactory = new WrapFactory();
Logger logger = new Logger();
Action<Product> log = new Action<Product>(logger.Log);
Box box1 = wrapFactory.WrapProduct(pizzaFactory, log);
Box box2 = wrapFactory.WrapProduct(toycarFactory, log);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
}
}
interface IProductFactory
{
Product Make();
}
class PizzaFactory : IProductFactory // 这个类实现了IProductFactory的接口
{
public Product Make()
{
// 重构是指基本不改变原来的代码,只是把代码放到更合适的地方去
Product product = new Product();
product.Name = "Pizza";
product.Price = 20;
return product;
}
}
class ToyFactory : IProductFactory
{
public Product Make()
{
Product product = new Product();
product.Name = "Toy Cat";
product.Price = 120;
return product;
}
}
class Logger
{
public void Log(Product product)
{
// Log 以回调的形式传进模版的方法里
Console.WriteLine("Product '{0}' created at {1}. Price is {2}.", product.Name, DateTime.UtcNow, product.Price);
}
}
class Product
{
public string Name {
get; set; }
public double Price {
get; set; }
}
class Box
{
public Product Product {
get; set; }
}
class WrapFactory
{
public Box WrapProduct(IProductFactory productFactory, Action<Product> logCallback)
{
// 模板方法
Box box = new Box();
Product product = productFactory.Make();
if (product.Price >= 50)
{
logCallback(product);
}
box.Product = product;
return box;
}
}
}
Pode-se observar que após a refatoração e o uso de interfaces, não há delegação no programa e não há acoplamento no nível do método;
este exemplo mostra que as interfaces podem ser usadas para substituir a delegação;
Seção 2 Incidente
Parte 1 Compreensão preliminar do evento
- Definição: Evento, traduzido como "evento"
-
- Algo que pode acontecer, principalmente algo mais importante;
-
- algo que acontece, especialmente algo importante.
-
- A explicação suave é “o que pode acontecer”, que é chamado de evento;
- Função: permite que um objeto ou classe tenhacapacidades de notificaçãoum membro de
-
- Na linguagem C#, um evento é um membro de um tipo que permite que um objeto ou classe forneça notificações
-
- Um evento é um membro que permite que um objeto ou classe forneça notificações.
-
- O objeto A possui um tempo B, o que significa que quando B ocorre, A tem a capacidade de notificar outros objetos;
-
- As mensagens enviadas através do evento e relacionadas ao próprio evento são chamadas de parâmetros de evento EventArgs
-
- O comportamento de executar ações com base em parâmetros simultâneos e de eventos é chamado de tempo de resposta ou processamento de eventos. O que é feito ao processar um evento é chamado de manipulador de eventos.
- Uso: usado para coordenação de ações e transferência de informações (envio de mensagens) entre objetos ou classes
-
- A função do evento = notificar outros objetos ou classes + parâmetros opcionais do evento (ou seja, informações detalhadas)
- Princípio: Dois "5" no modelo de evento (também chamado de modelo ocorrência-resposta)
-
- Cinco partes em "Ocorrência->Resposta": você levanta quando o despertador toca, você cozinha quando a criança está com fome... Existe uma relação implícita de "assinatura";
-
- Cinco ações em "Ocorrência->Resposta":
-
-
- (1) tenho um evento;
-
-
-
- (2) Uma pessoa ou grupo de pessoas se preocupa com o meu incidente;
-
-
-
- (3) Este meu incidente aconteceu;
-
-
-
- (4) As pessoas preocupadas com este incidente serão notificadas imediatamente;
-
-
-
- (5) A pessoa que foi notificada responde ao evento (também denominado “processamento do evento”) com base nas informações do evento recebidas (também denominadas “dados de tempo”, “parâmetros do evento”, “notificação”);
-
- A terminologia relevante precisa ser definida para facilitar a comunicação e a aprendizagem
-
- Inscritos no evento, são iguais ao receptor da mensagem do evento, ao respondedor do horário, ao manipulador do evento e ao objeto notificado pelo evento, o que facilita a comunicação.Use apenas assinantes de eventos;
-
- parâmetros de evento, que é igual a informações de evento, mensagem de evento e dados de evento e é fácil de comunicar.Use apenas parâmetros de evento;
- dica
-
- Os eventos são usados principalmente na programação do lado do cliente desenvolvida para desktops, telefones celulares, etc., porque esses programas clientes são frequentemente "conduzidos" pelos usuários por meio de eventos;
-
- O modelo de evento é abstraído do mundo real e várias linguagens de programação implementam esse mecanismo de diferentes maneiras;
-
- Não há membro de evento na linguagem Java, nem qualquer tipo de dados, como delegação. Os eventos Java são implementados usando interfaces;
-
- O modelo de evento é uma coisa boa, mas tem falhas. Se não houver restrições ao escrever, a lógica do programa será facilmente confundida. Após um longo período de resumo, resumi padrões de arquitetura como MVC, MVP e MVVM.Estes são padrões de eventos mais avançados e mais avançados.uso válido;
-
- Durante o desenvolvimento diário, há mais oportunidades de usar eventos existentes e menos oportunidades de declarar eventos você mesmo;
Parte 2 Inscrição em Evento
- Cinco componentes do modelo de evento
-
- O proprietário do evento, fonte do evento, objeto, o evento é acionado internamente pelo proprietário;
-
- Membros do evento, ou seja, o próprio evento, evento, membros
-
- O respondedor do evento, ou seja, o assinante do evento, o objeto.Quando o evento ocorre, quais objetos são notificados são os respondedores do evento;
-
- Manipulador de eventos, membro, é essencialmente um método de retorno de chamada
-
- A assinatura de eventos, que associa processadores de eventos a eventos, é essencialmente um “contrato” baseado no tipo de delegação.
- Perceber
-
- Manipuladores de eventos são membros do método
-
- Ao conectar um manipulador de eventos, você pode usar uma instância delegada ou usar diretamente o nome do método.Este é o açúcar de sintaxe;
-
- A assinatura do manipulador de eventos para o evento não é arbitrária e é verificada pela correspondência do tipo de delegado usado ao declarar o evento;
-
- Os eventos podem ser chamados de forma síncrona ou assíncrona;
Aqui está um pequeno exemplo
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
System.Timers.Timer timer = new System.Timers.Timer(); // 事件拥有者 timer
timer.Interval = 1000; // ms
Boy boy = new Boy(); // 事件的响应者是 boy 对象
Girl girl = new Girl();
timer.Elapsed += boy.Action;// += 是订阅的写法,后面要跟上事件响应者的事件处理器
timer.Elapsed += girl.Action;
// 事件 Elapsed,事件订阅 +=
timer.Start();
Console.ReadLine();
}
}
class Boy
{
// 事件的处理器
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Jump!");
}
}
class Girl
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Sing!");
}
}
}
A imagem acima mostra como é quando um evento tem dois manipuladores de eventos ao mesmo tempo;
A imagem acima mostra o modelo de mecanismo de evento padrão, que possui uma estrutura clara e é o protótipo de padrões de design como MVC e MVP. O programa
a seguir é uma explicação desse modelo de mecanismo de evento padrão.
namespace WindowsFormsApp1
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Form form = new Form(); // 事件的拥有者 form
Controller controller = new Controller(form); // 事件的响应者 controller
form.ShowDialog();
}
}
class Controller
{
private Form form;
public Controller(Form form)
{
if (form != null)
{
this.form = form;
this.form.Click += this.FormClicked; // 事件是 form 的 click,+=实现事件订阅
}
}
// 事件处理器
private void FormClicked(object sender, EventArgs e)
{
this.form.Text = DateTime.Now.ToString();
}
}
}
A imagem acima mostra que o objeto usa seu próprio método para assinar e processar seus próprios eventos; o
programa a seguir é uma explicação da imagem acima e também aborda qual derivação é
namespace WindowsFormsApp1
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
MyForm form = new MyForm(); // 事件的拥有者 form,事件的响应者也是 from
form.Click += form.FormClicked; // 事件是 Click,事件的订阅是 +=
form.ShowDialog();
}
}
class MyForm : Form // 派生,集成原有的方法之外还可以添加新的方法
{
// 事件处理器
internal void FormClicked(object sender, EventArgs e)
{
this.Text = DateTime.Now.ToString();
}
}
}
A figura acima mostra o mais comumente usado. A característica é que o proprietário do evento é um membro do campo do respondente do evento. É a assinatura de evento padrão e a estrutura de processamento no Windows. O programa a seguir é uma explicação do exemplo na figura acima
.
namespace WindowsFormsApp1
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
MyForm form = new MyForm();
form.ShowDialog();
}
}
// 事件的响应者是 MyForm 的对象
class MyForm : Form
{
private TextBox textBox;
private Button button; // button 是事件的拥有者,且为字段成员
public MyForm()
{
this.textBox = new TextBox();
this.button = new Button();
// 显示在 form 当中
this.Controls.Add(this.textBox);
this.Controls.Add(this.button);
this.button.Click += this.ButtonClicked; // 事件是 Click
// += 是事件的订阅
this.button.Text = "Say Hello!";
this.button.Top = 100;
}
// 事件的处理器
private void ButtonClicked(object sender, EventArgs e)
{
this.textBox.Text = "Hello World";
}
}
}
Parte 3 Declaração de Eventos
Método completo de declaração de eventoExemplo
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer(); // 事件的拥有者
Waiter waiter = new Waiter(); // 事件的响应者
customer.Order += waiter.Action; // 使用 Action 的方法作为 waiter 类型的事件处理器
// Order 事件 += 事件的订阅
customer.Action();
customer.PayTheBill();
}
}
public class OrderEventArgs : EventArgs
{
public string DishName {
get; set; }
public string Size {
get; set; }
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
public class Customer // 需要保证访问级别是一致的
{
private OrderEventHandler orderEventHandler;
// 事件 Order
public event OrderEventHandler Order
{
add
{
this.orderEventHandler += value;
}
remove
{
this.orderEventHandler -= value;
}
}
public double Bill {
get; set; }
public void PayTheBill()
{
Console.WriteLine("I will pay ${0}", this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit Dowm.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think.......");
Thread.Sleep(1000);
}
if (this.orderEventHandler != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.orderEventHandler.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn(); ;
this.SitDown();
this.Think();
}
}
// 事件的响应者
public class Waiter
{
internal void Action(Customer customer, OrderEventArgs e)
{
Console.WriteLine("I will serve you the dish - {0}", e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
Método simples de declaração de eventoExemplo
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer(); // 事件的拥有者
Waiter waiter = new Waiter(); // 事件的响应者
customer.Order += waiter.Action; // 使用 Action 的方法作为 waiter 类型的事件处理器
// Order 事件 += 事件的订阅
customer.Action();
customer.PayTheBill();
}
}
public class OrderEventArgs : EventArgs
{
public string DishName {
get; set; }
public string Size {
get; set; }
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
public class Customer // 需要保证访问级别是一致的
{
public event OrderEventHandler Order;
public double Bill {
get; set; }
public void PayTheBill()
{
Console.WriteLine("I will pay ${0}", this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit Dowm.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think.......");
Thread.Sleep(1000);
}
if (this.Order != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.Order.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn(); ;
this.SitDown();
this.Think();
}
}
// 事件的响应者
public class Waiter
{
internal void Action(Customer customer, OrderEventArgs e)
{
Console.WriteLine("I will serve you the dish - {0}", e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
Por que precisamos de eventos quando temos campos do tipo delegado?
- Os membros do evento podem tornar o relacionamento entre a lógica do programa e os objetos mais razoável e seguro;
Parte 4 Esclarecimento
Categoria da seção 3
A programação orientada a objetos possui três características principais: encapsulamento, herança e polimorfismo;
o que é uma classe
- É uma estrutura de dados estrutura de dados
- é um tipo de dados
namespace ConsoleHelloWorld {
class Program {
static void Main(string[] args) {
Student student = new Student(id: 1, name: "Tim"); // 创建实例
student.Report();
}
}
class Student {
public Student(int id, string name) {
this.ID = id; ;
this.Name = name;
}
// 析构器,手动释放资源
// 当程序关闭之前,变量 student 没人引用,系统会自动调用析构器
~ Student() {
Console.WriteLine("Bye bye, Release the system resources");
}
public int ID {
get; set; }
public String Name {
get; set; }
public void Report() {
Console.WriteLine($"I'm #{
this.ID} student, my name is {
this.Name}.");
}
}
}
Declaração de classe da parte 1
- As aulas podem ser declaradas nos três locais a seguir:
-
- Dentro de um namespace (mais comum)
-
- Fora do namespace explícito (na verdade declarado no namespace global)
-
- Dentro do corpo da classe (classes membros, ou classes aninhadas, também são usadas com mais frequência)
Declaração, definição é Definição, que pode ser misturada e compreendida em C# e Java, mas em C++ as duas não são a mesma coisa. Em C++, a declaração de uma classe é separada da definição da classe. Se você deseja aplicar uma classe em C++, você precisa referenciar o arquivo de cabeçalho, mas em C# e Java, isso não é necessário; portanto, no linguagem C#
,Declaração é definição
O nome da classe e o corpo da classe na nomenclatura da classe são indispensáveis
-
Os modificadores de classe são opcionais:
-
- novo
-
- público
-
- protegido
-
- interno
-
- privado
-
- abstrato
-
- selado
-
- estático
-
O novo modificador é adequado para classes aninhadas. Ele especifica que a classe oculta membros herdados com o mesmo nome. Se o novo modificador for usado em uma declaração de classe que não seja uma declaração de classe aninhada, causará um erro em tempo de compilação;
-
Os modificadores públicos, protegidos, internos e privados controlarão a acessibilidade da classe.Dependendo do contexto em que a classe é declarada,alguns destes modificadores podem não ser permitidos;
-
abstrato, selado e estático são sobre herança;
-
A classe pública é acessível a outros projetos, ou seja, outros Assemblies podem acessar as classes deste Assembléia;
-
As classes internas podem ser acessadas livremente no mesmo projeto, o que significa que o nível de acesso é limitado ao projeto, ou seja, Assembly;
-
classe privada só pode ser vista se esta classe for membro de outras classes
Parte 2 Herança de Classe
É muito importante para a programação orientada a objetos. Seus recursos mais significativos são encapsulamento, herança e polimorfismo, e o polimorfismo é baseado em herança;
A sintaxe para declarar uma classe herdada é relativamente simples:
class + 类名 : 类基础 + 类体
De modo geral, a classe base e a classe derivada são um par, e a classe pai e a subclasse são um par;
namespace ConsoleHelloWorld {
class Program {
static void Main(string[] args) {
Type type = typeof(Car);
Type baseType = type.BaseType;
Console.WriteLine(baseType.FullName);
// 可以看到输出 >> ConsoleHelloWorld.Vehicle
// 说明 Car 的基类是 Vehicle
// "是一个 is a" 概念:
// 一个派生类的实例,也是基类的实例,但反过来不正确
// 也就是 Car 的实例也是 Vehicle 的实例
// 也就是:一个汽车,也是一个交通工具
Car car = new Car(); // car 是实例,引用着 new 操作符创建的实例
Console.WriteLine(car is Vehicle);
// 可以看到输出 >> True
Vehicle vehicle = new Vehicle();
Console.WriteLine(vehicle is Car);
// 可以看到输出 >> False
// 从 "是一个 is a" 概念,引出 "可以用一个父类类型的变量,引用子类类型的实例"
// 在多态中是非常有用的,下面是引出概念的示例:
Vehicle vehicle1 = new Car();
Object o1 = new Vehicle();
Object o2 = new Car();
}
}
// 作为基类
// 如果声明一个类没有说基类是谁
// 下面的例子编译器等效 class Vehicle : Object { }
class Vehicle {
}
// 由 Vehicle 派生而来,Car 继承了 Vehicle
class Car : Vehicle {
}
}
requer atenção:
Se seal for usado para modificar uma classe, essa classe não poderá ser usada como classe base.
Uma classe em C# só pode ter no máximo uma classe base, mas pode ter várias interfaces base.
- Padronize a terminologia:
-
- Herdar/derivar de uma classe base;
-
- Uma determinada classe implementa uma determinada interface base;
O nível de acesso de uma subclasse não pode exceder o nível de acesso da classe pai
Parte 3 Herança e acesso de membros
A essência da herança: consiste na expansão horizontal e vertical da classe derivada nos membros existentes da classe base.
- A classe derivada é baseada nos membros existentes da classe base: quando ocorre a herança, a subclasse obtém todos os membros da classe pai. Quaisquer que sejam os membros da classe pai, a subclasse obtém todos eles;
- No processo de derivação e herança, a expansão é realizada: os membros da classe só podem tornar-se cada vez mais, mas não podem tornar-se cada vez menos;
- Os dois pontos acima levam a: uma vez que um membro da classe é introduzido na cadeia de herança, ele será transmitido o tempo todo e não poderá ser removido da cadeia de herança; esta é uma característica das linguagens estáticas;
- A expansão no número de membros da classe é uma expansão horizontal;
- Não aumente o número de membros da classe, mas expanda a versão de um determinado membro da classe, que é expansão vertical, ou seja, substituição;
Exemplo 1: Cadeia de herança
namespace ConsoleHelloWorld {
class Program {
static void Main(string[] args) {
RaceCar raceCar = new RaceCar();
string owner = raceCar.Owner; // Owner 声明在 Vehicle 当中,在继承链中继承下来了,这个 Owner 是去不掉的
}
}
// 基类
class Vehicle {
public string Owner {
get; set; }
}
class Car : Vehicle {
}
class RaceCar : Car {
}
}
Exemplo 2: O que é um objeto de classe base e como acessar os membros da classe base por meio do objeto de classe base
namespace ConsoleHelloWorld {
class Program {
static void Main(string[] args) {
Car car = new Car();
// 继承链上的类,当我们创建实例的时候,
// 先从基类的构造器开始,先构造基类对象,再一级一级构造
// 最终构造创建的子类对象
car.ShowOwner();
}
}
// 基类
class Vehicle {
// 实例构造器
public Vehicle(string owner)
{
this.Owner = owner;
}
public string Owner {
get; set; }
}
class Car : Vehicle {
public Car() : base("N/A") // 调用 Vehicle 构造器的时候传入一个值,或者和 Vehicle 构造器的参数列表一致
{
this.Owner = "Car Owner";
}
public void ShowOwner() {
Console.WriteLine($"this class owner value: {
this.Owner}"); // this. 子类对象上的值
Console.WriteLine($"base class owner value: {
base.Owner}"); // base. 基类对象上的值,base. 只能向上访问一层
// 在这个例子中,二者的值是一样的,因为 Car 继承下来了父类的全部成员,
// 所以 this.Owner 和 base.Owner 指向的使内存中同一个值
}
}
class RaceCar : Car {
}
}
O nível de acesso dos membros da classe é limitado ao nível de acesso da classe.
Parte 4 Estilo de implementação orientado a objetos
Encapsulamento baseado em classe e polimorfismo de herança;
Encapsulamento baseado em protótipo e polimorfismo de herança;
Parte 5 Reescrita e Polimorfismo
Após herdar da classe pai, a subclasse não aumenta o número de membros, mas substitui os métodos dos membros da classe pai;
Para substituir, você precisa marcar virtual nos membros da classe pai e marcar override nos membros da subclasse.
Se a classe pai não adicionar virtual e a subclasse não adicionar substituição, ela será chamada de subclasse que oculta os membros da classe pai.
O uso de ocultar membros de classe não é comum;
as condições para substituir e ocultar são: membros de função, visíveis (Precisa ser visível para as subclasses, ou seja, só fica visível quando é público ou protegido.), as assinaturas são consistentes
public é visível não apenas para subclasses, mas também para outras classes;
protected é visível apenas para subclasses e não para outras classes;
Polimorfismo: quando usamos variáveis de classe pai para nos referirmos a instâncias de subclasse, ao chamar membros substituídos, sempre podemos chamar os membros mais recentes da classe herdada;Ao chamar um membro do método, o que pode ser chamado é sempre a versão mais recente relacionada ao tipo de instância;
O polimorfismo é baseado no mecanismo de substituição (virtual -> substituição)
Aqui está um exemplo de substituição e polimorfismo:
using System;
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
var car = new Car();
car.Run();
// 可以看到输出为 >> Car is running!
var vehicle = new Vehicle();
vehicle.Run();
// 可以看到输出为 >> I'm running!
var vehicle2 = new RaseCar();
vehicle2.Run();
// 可以看到输出为 >> RaceCar is running!
// 涉及到多态
Vehicle v = new Car();
v.Run(); // 因为引用的是 Car 类型的实例
// 可以看到输出为 >> Car is running!
v.Walking();
// 可以看到输出为 >> I'm walking!
// 因为 Car 类型的 Walking 和 Vehicle 类型的 Walking 并没有重写关系
// 可以视为这个 Walking 中有两个方法,一个属于 Vehicle 一个属于 Car
}
}
class Vehicle
{
// 重写需要在父类的成员上标注 virtual,并在子类的成员上标注 override
public virtual void Run()
{
Console.WriteLine("I'm running!");
}
public void Walking()
{
Console.WriteLine("I'm walking!");
}
}
class Car : Vehicle
{
// 子类中的方法在父类中也有,并没有新增方法,只是更新了父类方法的版本
// 子类对父类成员的重写,也叫做成员的纵向扩展
public override void Run()
{
Console.WriteLine("Car is running!");
}
// 如果父类不添加 virtual,子类不添加 override,叫做子类对父类成员的隐藏
public void Walking()
{
Console.WriteLine("Car is walking");
}
}
class RaseCar : Car
{
// 继承链的重写
public override void Run()
{
Console.WriteLine("RaceCar is running!");
}
}
}
O exemplo a seguir é uma substituição de propriedades
using System;
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
Vehicle vehicle = new Car();
vehicle.Run();
Console.WriteLine(vehicle.Speed);
}
}
class Vehicle
{
private int _speed;
public virtual int Speed
{
get {
return _speed;}
set {
this._speed = value; }
}
// 重写需要在父类的成员上标注 virtual,并在子类的成员上标注 override
public virtual void Run()
{
Console.WriteLine("I'm running!");
this._speed = 100;
}
public void Walking()
{
Console.WriteLine("I'm walking!");
}
}
class Car : Vehicle
{
private int _rpm;
public override int Speed
{
get
{
return this._rpm / 100;
}
set
{
this._rpm = value / 100;
}
}
// 子类中的方法在父类中也有,并没有新增方法,只是更新了父类方法的版本
// 子类对父类成员的重写,也叫做成员的纵向扩展
public override void Run()
{
Console.WriteLine("Car is running!");
this._rpm = 5000;
}
// 如果父类不添加 virtual,子类不添加 override,叫做子类对父类成员的隐藏
public void Walking()
{
Console.WriteLine("Car is walking");
}
}
class RaseCar : Car
{
// 继承链的重写
public override void Run()
{
Console.WriteLine("RaceCar is running!");
}
}
}
Conhecimento ampliado das aulas da Seção 4
Interfaces e classes abstratas são as partes mais importantes do design orientado a objetos e são os dois pilares do design industrial de software:
princípios SÓLIDOS: princípio de responsabilidade única, princípio aberto-fechado, princípio de substituição de Liskov, princípio de isolamento de interface e princípio de inversão de dependência;
Parte 1 Aula abstrata
O código a seguir explica o que é uma classe abstrata e uma explicação simples do princípio aberto-fechado
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
}
}
// 抽象类:指的是函数成员没有被完全实现的类
abstract class Student
{
abstract public void Study(); // 没有被实现的函数成员一定用 abstract 关键字修饰,且不能用 private
// 被 abstract 修饰的方法,只有返回值,方法名和参数列表,没有方法体,是完全没有被实现的方法,是抽象方法
// 一旦一个类里有抽象方法或其他抽象成员,这个类就变成了抽象类,抽象类前面必须加上 abstract
// 因为抽象类含有未被实现的成员,因此编译器不允许实例化这个抽象类
// 两个作用:
// 1、作为基类,让别人从自己派生出去,在派生类实现没有实现的方法;
// 2、作为基类,声明变量,用基类类型的变量去引用子类类型的实例;
}
}
/*
* 为做基类而生的“抽象类”与“开放/关闭原则”
*
* 开闭原则:我们应该封装一些不变的、稳定的、固定的和确定的成员,
* 而把那些不确定的、有可能改变的成员声明为抽象成员,并留给子类去实现
*
* */
O exemplo a seguir é: "classe abstrata" e "princípio aberto/fechado" criados para classes base:
using System;
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
Vehicle v = new RaceCar();
// 抽象类的唯一能做的事情就是给别的类当基类,并且引用一些已经完全实现抽象成员的子类实例
v.Run();
}
}
// 程序既有抽象类,也遵守开闭原则
abstract class Vehicle
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
public void Fill()
{
Console.WriteLine("Pay and fill......");
}
public abstract void Run();
}
class Car : Vehicle
{
public override void Run() // 实现抽象方法的时候也需要加上 override
{
Console.WriteLine("Car is running......");
}
}
class Truck : Vehicle
{
public override void Run()
{
Console.WriteLine("Truck is running......");
}
}
class RaceCar : Vehicle
{
public override void Run()
{
Console.WriteLine("Race Car is running......");
}
}
}
Classe abstrata pura:
using System;
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
Vehicle v = new RaceCar();
// 抽象类的唯一能做的事情就是给别的类当基类,并且引用一些已经完全实现抽象成员的子类实例
v.Run();
}
}
// 纯抽象类,在 C# 中实际上就是接口
abstract class VehicleBase
{
abstract public void Stop();
abstract public void Fill();
abstract public void Run();
}
// 程序既有抽象类,也遵守开闭原则
abstract class Vehicle : VehicleBase
{
public override void Stop()
{
Console.WriteLine("Stopped!");
}
public override void Fill()
{
Console.WriteLine("Pay and fill......");
}
}
class Car : Vehicle
{
public override void Run() // 实现抽象方法的时候也需要加上 override
{
Console.WriteLine("Car is running......");
}
}
class Truck : Vehicle
{
public override void Run()
{
Console.WriteLine("Truck is running......");
}
}
class RaceCar : Vehicle
{
public override void Run()
{
Console.WriteLine("Race Car is running......");
}
}
}
Uma classe abstrata pura é uma interface em C#. Aqui está um exemplo
using System;
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
Vehicle v = new RaceCar();
// 抽象类的唯一能做的事情就是给别的类当基类,并且引用一些已经完全实现抽象成员的子类实例
v.Run();
}
}
// 纯抽象类,在 C# 中实际上就是接口
interface IVehicle
{
void Stop();
void Fill();
void Run();
}
// 类实现接口
// 通过 抽象类作为不完全的实现,将其作为基类再创建具体类
abstract class Vehicle : IVehicle
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
public void Fill()
{
Console.WriteLine("Pay and fill......");
}
abstract public void Run();
}
class Car : Vehicle
{
public override void Run() // 实现抽象方法的时候也需要加上 override
{
Console.WriteLine("Car is running......");
}
}
class Truck : Vehicle
{
public override void Run()
{
Console.WriteLine("Truck is running......");
}
}
class RaceCar : Vehicle
{
public override void Run()
{
Console.WriteLine("Race Car is running......");
}
}
}
- O que são interfaces e classes abstratas
-
- Interfaces e classes abstratas são produtos da engenharia de software. Se a engenharia de software não for seguida, aumentará a dificuldade de manutenção do código;
-
- Classe concreta -> classe abstrata -> interface: cada vez mais abstrata, cada vez menos coisas são implementadas internamente;
-
- classe abstrata éNão totalmente implementadoClasses lógicas (podem consistir em campos e membros não públicos, que representam lógica específica);
-
- As classes abstratas nascem para reutilização: são utilizadas exclusivamente como classes base e também possuem funções de desacoplamento;
-
- Encapsular certas coisas e abrir coisas incertas, adiar a implementação para subclasses apropriadas;
-
- A interface éNão implementadoClasses lógicas, classes virtuais puras, possuem apenas membros de função, e todos os membros são públicos e implicitamente públicos;
-
- A interface nasceu para desacoplamento: alta coesão, baixo acoplamento, conveniente para testes unitários;
-
- A interface é um protocolo conhecido há muito tempo na produção industrial;
-
- Eles não podem ser instanciados e só podem ser usados para declarar variáveis e referenciar instâncias de classes concretas;
Parte 2Interface
Interface: evoluída a partir de classes abstratas; os métodos membros na interface devem ser públicos, portanto, ao escrever, não há necessidade de declarar explicitamente público, o padrão é público; a essência da interface: o relacionamento entre o chamador do serviço e
o fornecedor do serviço O contrato deve ser visível para ambas as partes, por isso utiliza-se o público;
no mundo abstrato, é a divisão do trabalho e a cooperação entre classes e objetos, e essa cooperação é chamada de dependência. Quando ocorre a dependência, ocorre o acoplamento. Quanto mais direta for a dependência, mais direta será a dependência. Quanto mais estreito for o acoplamento, segue-se um exemplo para explicar a dependência e o acoplamento:
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
var engine = new Engine();
var car = new Car(engine);
car.Run(3);
Console.WriteLine(car.Speed);
// 可以看到输出结果为 >> 30
}
}
class Engine
{
public int RPM {
get; private set; }
public void Work(int gas)
{
this.RPM = 1000 * gas;
}
}
class Car
{
// 此时 Car 和 Engine 类型紧耦合在一起,Car 依赖在 Engine 上
// 紧耦合的问题在于,基础的类如果出问题,会导致依赖这个类的类出现问题;
// 紧耦合会导致调试难度升高,这就是紧耦合带来的弊端;
private Engine _engine;
public Car(Engine engine)
{
_engine = engine;
}
public int Speed {
get; private set; }
public void Run(int gas)
{
_engine.Work(gas);
this.Speed = _engine.RPM / 100;
}
}
}
Como resolver o problema do acoplamento rígido? Isso é para apresentar uma interface. Aqui está um exemplo:
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
var user = new PhoneUser(new EricssonPhone());
// 只在这块换了类名,而 User class 还有其他的代码都没变动
// 引入接口之后,耦合变得非常的松,松耦合
user.UsePhone();
}
}
class PhoneUser
{
private IPhone _phone;
public PhoneUser(IPhone phone)
{
_phone = phone;
}
public void UsePhone()
{
_phone.Dail();
_phone.PickUp();
_phone.Send();
_phone.Receive();
}
}
interface IPhone
{
void Dail();
void PickUp();
void Send();
void Receive();
}
class NokiaPhone : IPhone
{
public void Dail()
{
Console.WriteLine("Nokia calling ...");
}
public void PickUp()
{
Console.WriteLine("Hello! This is Tim!");
}
public void Send()
{
Console.WriteLine("Nokia message ring ...");
}
public void Receive()
{
Console.WriteLine("Hello!");
}
}
class EricssonPhone : IPhone
{
public void Dail()
{
Console.WriteLine("Ericsson calling ...");
}
public void PickUp()
{
Console.WriteLine("Hello! This is Jack!");
}
public void Send()
{
Console.WriteLine("Ericsson ring ...");
}
public void Receive()
{
Console.WriteLine("Good morning!");
}
}
}
As interfaces nascem para acoplamento fraco e para compreensão do acoplamento.
Parte 3 Princípio de Teste de Unidade e Inversão de Dependência
O teste unitário é uma aplicação direta do princípio de inversão de dependência no desenvolvimento;
princípio de inversão de dependência: módulos de alto nível não devem depender de módulos de baixo nível, ambos devem confiar em suas abstrações; abstrações (interfaces) não devem depender de detalhes (implementação de funções específicas), detalhes Devem contar com abstração (escrever programas de implementação em formato de interface);
isto éVocê deve programar para a interface, não para a implementação
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
var fan = new DeskFan(new PowerSupply());
Console.WriteLine(fan.Work());
}
}
class PowerSupply
{
public int GetPower()
{
return 210;
}
}
class DeskFan
{
private PowerSupply _powerSupply;
public DeskFan(PowerSupply powerSupply)
{
_powerSupply = powerSupply;
}
public string Work()
{
int power = _powerSupply.GetPower();
if (power <= 0)
{
return "Won't work.";
}
else if (power < 100)
{
return "Slow";
}
else if (power < 200)
{
return "Work fine";
}
else
{
return "Warning!";
}
}
}
}
Você pode ver que o exemplo acima é um exemplo fortemente acoplado.Quando queremos modificar o valor da potência de tensão, precisamos modificar o valor em PowerSupply. Depois que uma classe é projetada, o código da classe não pode ser modificado diretamente. Para resolver esse problema de acoplamento forte, são introduzidas interfaces para desacoplamento e testes unitários são usados para testar o DeskFan. A seguir está o código modificado;
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
var fan = new DeskFan(new PowerSupply());
Console.WriteLine(fan.Work());
}
}
public interface IPowerSupply
{
int GetPower();
}
public class PowerSupply : IPowerSupply
{
public int GetPower()
{
return 210;
}
}
public class DeskFan
{
private IPowerSupply _powerSupply; // 所有的耦合类型都变成接口类型;
public DeskFan(IPowerSupply powerSupply)
{
_powerSupply = powerSupply;
}
public string Work()
{
int power = _powerSupply.GetPower();
if (power <= 0)
{
return "Won't work.";
}
else if (power < 100)
{
return "Slow";
}
else if (power < 200)
{
return "Work fine";
}
else
{
return "Explode!";
}
}
}
}
A seguir está um exemplo de teste unitário: é também a aplicação de interfaces em testes unitários;
using NUnit.Framework;
using HelloRider;
namespace InterfaceExample.Tests
{
public class DeskFanTests
{
[SetUp]
public void Setup()
{
}
[Test]
public void PowerLowerThanZero_OK() // 测试电压为 0 时的测试结果是否符合预期
{
var fan = new DeskFan(new PowerSupplyLowerThanZero());
var expected = "Won't work.";
var actual = fan.Work();
Assert.AreEqual(expected, actual);
}
[Test] // 测试例子必须加上 [Test]
public void PowerHigherThan200_Warning() // 测试电压超过 200 时的测试结果是否符合预期
{
var fan = new DeskFan(new PowerSupplyHigherThan200());
var expected = "Warning!";
var actual = fan.Work();
Assert.AreEqual(expected, actual);
}
}
class PowerSupplyLowerThanZero : IPowerSupply
{
public int GetPower()
{
return 0;
}
}
class PowerSupplyHigherThan200 : IPowerSupply
{
public int GetPower()
{
return 220;
}
}
}
A imagem acima mostra os resultados da execução do teste de unidade. Você pode ver no canto inferior esquerdo que um caso de teste foi aprovado e um caso de teste falhou. Portanto, você pode interromper a depuração do ponto na parte da amostra de teste onde o problema ocorre;
Parte 4 Princípio de isolamento de interface
A violação do princípio de isolamento da interface tem duas consequências:
Exemplo 1:
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
var driver = new Driver(new Car());
driver.Drive();
}
}
// 当把包含过多功能的接口类型,传给功能调用者的时候
// 必然有一部分功能是用不到的,违反了接口隔离原则
// 实现这个接口的类,同时违反了单一职责原则
// 针对这个问题的解决方案,把接口拆分为多个小接口,
// 本质就是把不同的功能分离开,封装成接口
class Driver
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)
{
_vehicle = vehicle;
}
public void Drive()
{
_vehicle.Run();
}
}
interface IVehicle
{
void Run();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("Car is running");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("Truck is running");
}
}
interface ITank
{
void Fire();
void Run();
}
class LightTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!");
}
public void Run()
{
Console.WriteLine("Light Tank is running");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!");
}
public void Run()
{
Console.WriteLine("Medium Tank is running");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!!!!");
}
public void Run()
{
Console.WriteLine("Heavy Tank is running");
}
}
}
Se você quiser passar a interface ITank neste momento, você precisa modificar o código da classe Driver, o problema neste momento é que uma interface gorda é passada e existe uma função Fire que nunca será usada; agora, esse design viola o princípio de isolamento da interface e a interface gorda Quando a interface é mesclada a partir de duas coisas essencialmente diferentes, a interface gorda deve ser dividida em duas interfaces, ou seja, Fogo e Corrida são separados; Fogo pertence à classe de armas, e Run pertence à classe de veículos motorizados; a seguir está o código modificado
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
var driver = new Driver(new Car());
var driver = new Driver(new Truck());
var driver = new Driver(new HeavyTank());
// 可以看到,这个 Driver 只要求能跑,修改完代码后,
// 无论是 Car、Truck还是Tank,都可以跑
// Driver 只调用 Run,不调用其他无关的,符合了接口隔离原则
driver.Drive();
}
}
// 当把包含过多功能的接口类型,传给功能调用者的时候
// 必然有一部分功能是用不到的,违反了接口隔离原则
// 实现这个接口的类,同时违反了单一职责原则
// 针对这个问题的解决方案,把接口拆分为多个小接口,
// 本质就是把不同的功能分离开,封装成接口
class Driver
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)
{
_vehicle = vehicle;
}
public void Drive()
{
_vehicle.Run();
}
}
interface IVehicle
{
void Run();
}
interface IWeapon
{
void Fire();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("Car is running");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("Truck is running");
}
}
interface ITank : IVehicle, IWeapon // 一个接口对多个接口的继承
{
}
class LightTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!");
}
public void Run()
{
Console.WriteLine("Light Tank is running");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!");
}
public void Run()
{
Console.WriteLine("Medium Tank is running");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!!!!");
}
public void Run()
{
Console.WriteLine("Heavy Tank is running");
}
}
}
Quando o princípio do isolamento da interface e o princípio da responsabilidade única são perseguidos demais, serão produzidas muitas interfaces e classes muito fragmentadas com apenas um método.Portanto, é necessário dominar o equilíbrio e manter um tamanho equilibrado de interfaces e classes.
Exemplo 2: A interface gorda passada para o chamador é em si uma fusão de duas interfaces pequenas que foram originalmente bem projetadas. Era para ser passada para uma interface pequena, mas no final foi passada para uma interface grande que fundiu o pequeno interfaces; os provedores de serviços qualificados originais ficam de fora; exemplos e sugestões de modificação abaixo
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
int[] nums1 = {
1, 2, 3, 4, 5};
ArrayList nums2 = new ArrayList {
1, 2, 3, 4, 5};
Console.WriteLine(Sum(nums1));
Console.WriteLine(Sum(nums2));
var nums3 = new ReadOnlyCollection(nums1);
foreach (var n in nums3)
{
Console.WriteLine(n);
}
Console.WriteLine(Sum(nums3)); // 此时 Sum 函数无法处理 nums3
// 虽然现在只用得到迭代,但现在传入的使 ICollection
// 把一些合格的 Service Provider 挡在外面
// 只需要把 Sum 传入的 ICollection 改为 IEnumerable 即可
// 这时符合接口隔离原则:调用者绝不多要用不着的功能
// static int Sum(IEnumerable nums)
}
static int Sum(ICollection nums)
{
int sum = 0;
foreach (var num in nums)
{
sum += (int) num;
}
return sum;
}
}
// 只能用于迭代,不能添加也不能删除的集合
class ReadOnlyCollection : IEnumerable
{
private int[] _array;
public ReadOnlyCollection(int[] array)
{
_array = array;
}
public IEnumerator GetEnumerator()
{
return new Enumerator(this);
}
// 成员类
public class Enumerator : IEnumerator
{
private ReadOnlyCollection _collection;
private int _head;
public Enumerator(ReadOnlyCollection collection)
{
_collection = collection;
_head = -1;
}
public bool MoveNext()
{
if (++_head < _collection._array.Length)
{
return true;
}
else
{
return false;
}
}
public void Reset()
{
_head = -1;
}
public object Current
{
get
{
object o = _collection._array[_head];
return o;
}
}
}
}
}
Exemplo 3:Dedicado a mostrar o que há de único em C#: implementação explícita de interface
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
var wk = new WarmKiller();
wk.Love();
wk.Kill();
// 此时,Love 和 Kill 方法都轻易能被调用
var wk2 = new WarmKillerAAA();
wk2.Love(); // 此时只能看到 Love 看不到 Killer
IKiller killer = new WarmKillerAAA();
killer.Kill(); // 此时才能看到 Killer
// 如果此时要用 Love 方法
var wk3 = killer as WarmKiller; // 强制类型转换
wk3.Love(); // 就能看到 Love 方法了
}
}
interface IGentleman
{
void Love();
}
interface IKiller
{
void Kill();
}
// 普通的接口隔离与普通的类与实现
class WarmKiller : IGentleman, IKiller
{
public void Love()
{
Console.WriteLine("I will love you for ever ...");
}
public void Kill()
{
Console.WriteLine("Let me kill you ...");
}
}
// 普通的接口隔离与普通的类与实现
class WarmKillerAAA : IGentleman, IKiller
{
public void Love()
{
Console.WriteLine("I will love you for ever ...");
}
// 只有把这个类的实例,当做 IKiller 类型的实例的时候,这个方法才能被调用
// 也就是 只有 Killer 类型的变量来引用 WarmKillerAAA 类,类型的时候,这个方法才能被调用
void IKiller.Kill()
{
Console.WriteLine("Let me kill you ...");
}
}
}
Parte 5 Reflexão (a ser adicionada)
A reflexão não é uma função da linguagem C#, mas uma função do framework .Net.
O exemplo a seguir é o princípio básico da reflexão
class Program {
static void Main(string[] args)
{
ITank tank = new HeavyTank();
// ==========================
// 分割线以下不再使用静态类型
// 完全是从内存中读取出动态的类型的描述,MetaData 和方法的表述
// 这块展示的使反射的基本原理
var t = tank.GetType();
object o = Activator.CreateInstance(t);
MethodInfo fireMi = t.GetMethod("Fire");
MethodInfo runMi = t.GetMethod("Run");
fireMi.Invoke(o, null);
runMi.Invoke(o, null);
}
}
class Driver
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)
{
_vehicle = vehicle;
}
public void Drive()
{
_vehicle.Run();
}
}
interface IVehicle
{
void Run();
}
interface IWeapon
{
void Fire();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("Car is running");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("Truck is running");
}
}
interface ITank : IVehicle, IWeapon // 一个接口对多个接口的继承
{
}
class LightTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!");
}
public void Run()
{
Console.WriteLine("Light Tank is running");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!");
}
public void Run()
{
Console.WriteLine("Medium Tank is running");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!!!!");
}
public void Run()
{
Console.WriteLine("Heavy Tank is running");
}
}
No trabalho real, a maioria dos contatos são reflexões encapsuladas.
Uma das funções mais importantes da reflexão encapsulada é a injeção de dependência (DI);
Parte 6 Genérico (a ser adicionado)
Os genéricos estão em toda parte e são equivalentes às interfaces na programação orientada a objetos;
por que os genéricos são necessários? Evite expansão de membros ou expansão de tipo.
Ortogonalidade: tipos genéricos, membros genéricos.
Coisas genéricas não podem ser usadas diretamente durante a programação. Elas precisam ser especializadas antes de poderem ser usadas.
Depois que uma classe genérica é especializada, sempre que parâmetros de tipo são usados, eles são todos fortemente tipados.O
exemplo a seguir é:As classes genéricas não produzem inflação de tipo nem expansão de membros.
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
Apple apple = new Apple() {
Color = "Red"};
Box<Apple> box1 = new Box<Apple>() {
Cargo = apple};
Console.WriteLine(box1.Cargo.Color);
// 可以看到输出为 >> Red
Book book = new Book() {
Name = "New Book"};
Box<Book> box2 = new Box<Book>() {
Cargo = book};
Console.WriteLine(box2.Cargo.Name);
// 可以看到输出为 >> New Book
}
}
class Apple
{
public string Color {
get; set; }
}
class Book
{
public string Name {
get; set; }
}
class Box<TCargo> // 普通类改成泛型类就是在类名后加上尖括号,在尖括号内写上类型参数
{
public TCargo Cargo {
get; set; }
}
}
O exemplo a seguir é um exemplo de interface genérica:
namespace HelloRider
{
class Program {
static void Main(string[] args)
{
Student<int> student = new Student<int>();
student.ID = 101;
student.Name = "Tim";
Teacher teacher = new Teacher();
teacher.ID = 1001;
teacher.Name = "Cheems";
}
}
interface IUnique<TId>
{
public TId ID {
get; set; }
}
// 如果一个类实现的是泛型接口,那么这个类本身也是泛型的
// 这个类是实现泛型接口,所以成了泛型类 {第一个方法}
class Student<TId> : IUnique<TId>
{
public TId ID {
get; set; }
public string Name {
get; set; }
}
// 在实现泛型接口的时候,是实现特化之后的泛型接口,那么这个类就不再是泛型类 {第二个方法}
class Teacher : IUnique<ulong>
{
public ulong ID {
get; set; }
public string Name {
get; set; }
}
}
A razão pela qual os genéricos têm um amplo impacto na programação é que quase todas as estruturas de dados comumente usadas em C# são genéricas;