O caminho para OOP: a perspectiva de um engenheiro

aviso Legal



O artigo não implica nenhuma visão fundamentalmente nova das coisas, exceto do ponto de vista de estudar este material do "zero absoluto".





O material é baseado em notas de cerca de 7 anos atrás, quando minha trajetória de estudar POO sem educação em TI estava apenas começando. Naquela época, MATLAB era a linguagem principal, muito mais tarde mudei para C #.



A afirmação dos princípios da OOP, que encontrei, com exemplos na forma de algumas maçãs, peras herdadas da classe "fruta" e um monte de terminologia (herança, polimorfismo, encapsulamento, etc.), foi percebida como uma alfabetização chinesa.



Pelo contrário, agora por alguma razão eu percebo esse material normalmente, e a apresentação de meu próprio artigo às vezes parece confusa e longa.



Mas minhas anotações antigas e o código horrível que sobreviveu nos holodiscos do pipboy mostram que a apresentação "clássica" não cumpria suas funções naquela época e foi completamente malsucedida. Talvez haja algo nisso.



O quanto isso corresponde à realidade e às suas próprias preferências - decida por si mesmo ...



Condições prévias para OOP



Wall code



Quando comecei a escrever em MATLAB'e, era a única forma de escrever e sabia como. Eu conhecia as funções e que o programa pode ser dividido em partes.



O problema era que todos os exemplos eram uma merda. Eu abri o livro do curso de alguém, vi ali pequenas funções corporais de 2-3 linhas, no total tudo isso NÃO funcionou (faltava algo), e só funcionou quando remontei esse lixo em uma "parede".



Então, escrevi alguns pequenos programas várias vezes e, a cada vez, me perguntei por que havia algo para compartilhar. Só mais tarde veio o entendimento: o código "parede" é o estado normal de um programa de cerca de 1,5 páginas A4. Sem funções e, Deus me livre, OOP NÃO é necessário lá.



É assim que o script Matlab se parece (retirado da Internet).



Fs = 1000;                   % Sampling frequency
T = 1/Fs;                      % Sample time
L = 1000;                      % Length of signal
t = (0:L-1)*T;                % Time vector
% Sum of a 50 Hz sinusoid and a 120 Hz sinusoid
%x = 0.7*sin(2*pi*50*t) + sin(2*pi*120*t); 
%y = x + 2*randn(size(t));     % Sinusoids plus noise
y=1+sin(100*pi*t);
plot(Fs*t(1:50),y(1:50))
title('Signal Corrupted with Zero-Mean Random Noise')
xlabel('time (milliseconds)')
figure
NFFT = 2^nextpow2(L); % Next power of 2 from length of y
Y = fft(y,NFFT)/L;
f = Fs/2*linspace(0,1,NFFT/2+1);
% Plot single-sided amplitude spectrum.
plot(f,2*abs(Y(1:NFFT/2+1))) 
title('Single-Sided Amplitude Spectrum of y(t)')
xlabel('Frequency (Hz)')
ylabel('|Y(f)|')


Dividindo o código em funções



Por que o código ainda está sendo dividido em pedaços, eu adivinhei quando seu volume começou a se tornar completamente inimaginável (agora eu encontrei um código de merda no arquivo - 650 linhas por uma parede). E então me lembrei das funções. Eu sabia que eles permitiam que você dividisse seu código em pequenos blocos que são mais fáceis de depurar e reutilizar.



Mas o truque é diferente - por alguma razão, todos os materiais de ensino são silenciosos sobre QUANTO uma função de variáveis ​​tem ...



Um curso de matemática disse que uma função é y = f (x)



Isso é chamado de "função de uma variável." Por exemplo, y = x 2 é um PARABOL completo!

Problema matemático: construa um PARABOL por pontos. Em uma folha de caderno, em uma caixa.

. z=f(x,y). — — . , .. . .









, « », . , , . – . .



-…



imagem


E se a função tiver quatro ou mais variáveis…. Teoria das supercordas. Variedade Calabi-Yau. Mortal. Não fornecido. Entenda ...



Resumindo, está tudo errado. Na programação, o estado normal de uma função é duplo vaginal duplo anal . Ele pega 100 variáveis ​​e retorna o mesmo, o que é bom. Outra coisa é anormal - listá-los com uma COMMA.



imagem


Sobre o fato de que você pode escrever de alguma forma diferente, eu percebi quando naveguei AQUI ESTE




function work = SelectFun(ProtName,length_line,num_length,angleN_1,angleN_2,num_angleN,angleF_1,angleF_2,num_angleF, res_max, num_res,varargin)
global angleF angleN model_initialized


Um monte de variáveis ​​separadas por uma COMMA. E o código de chamada tem nomes completamente diferentes para esses parâmetros, algo como SelectFun (a, b, c, d ...) Portanto, você precisa se lembrar onde está a variável. E faça seu arranjo através do COMMA. E se o código está sendo modernizado, e o número de variáveis ​​muda, então elas devem ser reorganizadas com uma COMMA.



E por que variáveis ​​globais (disparem!) Nesta miséria?



Bingo! Para não organizar variáveis ​​a cada atualização de código por meio de COMMA.



Mas o COMMA ainda me seguia como um pesadelo.



imagem


E varargin apareceu. Isso significa que posso adicionar muitos mais argumentos no código de chamada, separados por uma COMMA ...



E então pensei em matrizes. Exemplos de tutoriais falaram com entusiasmo sobre o fato de que uma matriz pode ser assim:




=
[1 2 3
 4 5 6
 7 8 9]


E você vê, X (2,3) = 6 e X (3,3) = 9, e nós ... podemos organizar a multiplicação de matrizes em tais arrays! Na última lição nós passamos por PARABOLS, e agora MATRIXES….



E nem uma única linha desses malditos livros é curta e clara: você precisa de arrays para fazer uma função de 100 variáveis ​​e não cair de sua listagem por meio de uma COMMA.



imagem


Em geral, tive a ideia de amontoar tudo em uma grande mesa bidimensional. Tudo correu bem no início:




angles =
[angleN, angleN_1, angleN_2, num_angleN
 angleF, angleF_1, angleF_2, num_angleF]

function work= SelectFun(ProtName, length_line, num_length, angles , res_max, num_res, varargin)


Mas eu queria mais. E começou a ficar assim:




data=
[angleN, angleN_1, angleN_2, num_angleN
 angleF, angleF_1, angleF_2, num_angleF
length_line, num_length,  0, 0 
res_max,num_res, 0,0]
function work= SelectFun(ProtName,data,varargin)


E tudo parece estar bem, mas ... ZERO! Eles apareceram porque eu queria espalhar dados heterogêneos em linhas diferentes, e a quantidade de dados de tipos diferentes era diferente ... E como a função deve processar esses zeros? O que acontece se eu quiser atualizar o código? Vou ter que reescrever o manipulador para esses zeros desagradáveis ​​dentro da função! Afinal, algumas das variáveis ​​podem na verdade ser iguais a zero ...



Nunca perguntei por isso ...



Em geral, foi assim que aprendi sobre ESTRUTURAS.



Estruturas



Foi aqui que foi necessário iniciar a apresentação sobre métodos de empacotamento de dados. As matrizes com uma "mesa", ao que parece, historicamente surgiram primeiro, e eles escrevem sobre elas também - no início. Na prática, você pode encontrar muitos programas em que os arrays são unidimensionais ou não são.

A estrutura é um empacotamento de dados em "pasta de arquivos", aproximadamente no disco rígido de um computador.

Unidade D: \

X (pasta de variável - "objeto" ou "estrutura")

- a.txt (arquivo de variável com dados - "campo de objeto", campo inglês. Número 5

é armazenado) - b.txt (número 10 é armazenado )

- .txt

Y (variável-subpasta - "objeto")

- d.txt (o número 2 é armazenado)

- e.txt



Para ficar mais claro, vamos escrever como veríamos o caminho para o arquivo d.txt no Windows Explorer

D: \ X \ Y \ d.txt


Depois disso, abrimos o arquivo e escrevemos o número "2" nele.

Agora - como ficará no código do programa. Não há necessidade de se referir à "unidade raiz local", então D: \ simplesmente não está lá, nem teremos uma extensão de arquivo. Quanto ao resto, geralmente é usado um ponto final em vez de uma barra \ na programação.

Acontece assim:




X.Y.d=2
%   
X.a=5
X.b=10 
 - 
X.c=X.a+X.b    %..  .=5+10=15
X.Y.e=X.c*X.Y.d    %.. X.Y.e=15*2=30


No matlab, as estruturas ( struct ) podem ser criadas na hora, sem sair do checkout, ou seja, o código acima é executável, você pode colocá-lo no console e tudo funcionará imediatamente. A estrutura aparecerá imediatamente e todos os "arquivos de variáveis" e "subpastas de variáveis" serão adicionados de uma vez. Infelizmente, é impossível dizer isso sobre C #, a estrutura ( estrutura ) é definida lá por hemorróidas.



A estrutura é um parente mais legal do ARRAY OF TABLES, onde em vez de índices - um sistema de pasta de arquivos. Estrutura = "pasta de variáveis", que contém "arquivos de variáveis" e outras "pastas de variáveis" (ou seja, uma espécie de subpastas).



Tudo é familiar, tudo é exatamente igual a um computador, pastas, arquivos nelas, só que em arquivos não há fotos, mas números (embora fotos também sejam possíveis).



Esta é uma versão mais avançada de armazenamento de dados para passar para uma FUNCTION em comparação com a ideia de fazer uma ARRAY TABLE, especialmente bidimensional, e, me incomoda, um tesseract, tridimensional e mais.

O ARRAY TABLE pode ser usado em dois casos:

- é pequeno (por que é então? O quê, você não pode passar argumentos separados por vírgulas para a função?).

- você pode fazer um loop nele e automatizar a pesquisa / preenchimento (isso nem sempre é possível)

Na realidade, ARRAY TABLE é geralmente usado apenas como uma linha unidimensional de dados homogêneos. Todo o resto em programas normais é feito de acordo com o esquema de "pasta de arquivos".



Então por que livros de programação começam com matrizes e tabelas? !!!



Em suma, "tendo descoberto" as estruturas por mim mesmo, decidi que havia encontrado uma mina de ouro e reescrevi tudo com urgência. O código de merda começou a ficar mais ou menos assim:




Data.anglesN=[angleN, angleN_1,angleN_2, num_angleN]; %  
Data.anglesF=[angleF, angleF_1, angleF_2, num_angleF]; %  
Data.length_line= length_line;
Data.num_length= num_length;
Data.res_max= res_max;
Data.num_res= num_res;
function work= SelectFun(ProtName,Data,varargin)


Sim, você pode fazer perfeccionismo aqui e fazer um monte de objetos aninhados, mas esse não é o ponto. O principal é que agora, dentro da função, a variável é indexada não pelo seu número ordinal (onde está na lista de argumentos, separada por uma COMMA), mas pelo nome. E não há zeros burros. E a chamada da função agora está em uma forma aceitável, existem apenas 2 COMANDOS, você pode expirar calmamente.



Aulas



O conceito de "classe" trouxe uma tonelada de terminologia para mim: encapsulamento, herança, polimorfismo, métodos estáticos, campos, propriedades, métodos comuns, construtor ... # @% !!! ..

Por inexperiência, tendo descoberto as estruturas, decidi que não havia necessidade de complicar entidades desnecessariamente, e pensei - "as classes são como as mesmas estruturas, só que mais complicadas."



Até certo ponto, é assim. Mais precisamente, isso é exatamente o que é. Uma classe, se você olhar muito profundamente, é uma ESTRUTURA (um descendente ideológico de um array por uma tabela), que é criada quando o programa é INICIADO (em geral, parece ser, e não apenas na inicialização). Como em qualquer descendente de ARRAY TABLE, os dados são armazenados lá. Eles podem ser acessados ​​durante a execução do programa.



Portanto, minha primeira aula foi algo assim (estou escrevendo um exemplo em C #, em matlab, campos estáticos não são normalmente implementados, apenas por meio de uma curva de hack com variáveis ​​persistentes em uma função estática).



public class Math{
	public static double pi;
	public static double e;

	public static double CircleLength(double R){   //.. « »
	return 2*Math.pi*R; //  
    }
}


O caso acima é, por assim dizer, a habilidade "básica" de uma classe - ser estupidamente um array (estrutura) com dados. Esses dados são inseridos nele no início do programa e, a partir daí, podem ser extraídos, exatamente da mesma maneira que os extraímos da estrutura acima. A palavra-chave estática é usada para isso .



Uma estrutura -> é criada em qualquer lugar e armazena dados que são inseridos nela sempre que você quiser.Uma



classe -> é uma estrutura que é criada quando o programa é iniciado. Todos os campos marcados com a palavra estático simplesmente armazenam dados, como em uma estrutura normal. Os métodos estáticos são simplesmente funções que são chamadas de uma classe, assim como de uma pasta.




double L=Math.CircleLength(10); //L=62,8
Math.pi=4; //


Eu tinha uma piada - se os campos são variáveis ​​e os métodos são funções, como eles são armazenados em um só lugar? Pelo que entendi, uma função (método) em uma classe não é, na verdade, uma função, mas um ponteiro para uma função. Essa. é quase a mesma "variável" que pi em termos de trabalhar com ele.

Resumindo, no começo eu entendi as classes exatamente em tal volume e escrevi outro código de merda, onde APENAS funções estáticas eram usadas. Fora isso, por ser uma pasta com funções, não usei classes.



Este ponto também foi facilitado pelo fato de que é exatamente assim que as aulas são feitas no MATLAB - como uma pasta idiota cujo nome começa com @ (como @Math, sem espaço), dentro dela, os arquivos reais com a extensão .m são funções (métodos) e há arquivo de cabeçalho com a extensão .m, o que explica que a função CircleLength realmente pertence à classe, e não é apenas um arquivo .m com uma função não OOP inserida nele.

@ Math% pasta

- arquivo de cabeçalho

Math.m% - arquivo de função CircleLength.m%

Sim, existe uma maneira mais familiar de uma pessoa normal escrever uma classe em um arquivo .m, mas a princípio eu não sabia sobre ela. Os campos estáticos no matlab são apenas constantes e são escritos uma vez quando o programa é iniciado. Provavelmente para se proteger contra a "rede de arrasto", que decide atribuir Math.pi = 4 (IMHO, tópico absolutamente inútil e estúpido, nenhuma pessoa normal escreverá um grande projeto em matlab, e um programador irá depurar um pequeno projeto e, portanto, é improvável ele é um idiota).



Mas voltando ao assunto. Além dos métodos estáticos, a classe também possui um construtor. Um construtor é basicamente apenas uma função como y = f (x) ou mesmo y = f (). Pode não ter argumentos de entrada, deve haver argumentos de saída e esta é sempre uma nova estrutura (array).



O que o construtor faz. Ele apenas faz estruturas. Logicamente, é assim:



Código C # Equivalente booleano aproximado (pseudocódigo)


class MyClass {
    int a;
    int b;
    public  MyClass() {
	this.a=5;
	this.b=10;
    }
}



class MyClass {
    public  static MyClass MyClass() {
        int this.a=5;
        int this.b=10;
        return this;
    }
}



//… -   
var Y=new MyClass();	



//… -   
var Y= MyClass.MyClass();	






Cague o código no matlab, criando estruturas semelhantes sem nenhuma classe (onde a classe está presente - veja abaixo):




function Y=MyClass() %  MyClass,   Y=F()
    Y.a=5
    Y.b=10
end
… -   
Y=MyClass()


E na saída temos a estrutura

Y (variável de pasta)

- a (variável de arquivo, igual a 5)

- b (variável de arquivo igual a 10)

A partir disso, de fato, fica claro que os chamados campos de classe (não estáticos, sem o código de chave estático ) são variáveis ​​locais declaradas dentro da função construtora. O fato de serem escritos para algum tipo de demônio não no construtor, mas fora, é AÇÚCAR SINTAXICO.



SYNTAX SUGAR - esses recursos de merda de uma linguagem de programação, quando o código começa a parecer que eles querem ofuscá-lo no momento em que é escrito. Mas, por outro lado, torna-se mais curto e mais rápido (supostamente) escrito.



Tendo feito esta "descoberta", eu, que na altura escrevia apenas no Matlab, fiquei incrivelmente surpreendido.



No matlab, como escrevi acima, essas estruturas podem ser criadas no local, sem quaisquer construtores, simplesmente escrevendo Ya = 5 , Yb = 10, assim como você no sistema operacional pode criar arquivos e pastas sem sair da caixa registradora.



E aqui - algum tipo de "construtor", e todos os campos da estrutura (no matlab são chamados de propriedades - propriedades, embora, estritamente falando, as propriedades sejam uma coisa mais suja do que os campos) precisam ser burocraticamente escritos no arquivo de cabeçalho. Pelo que? O único benefício que vi neste sistema é que os campos da estrutura são predefinidos, e isso é como "autodocumentação" - você sempre pode ver o que deveria estar e o que não deveria estar. Aqui está algo assim que escrevi então:




classdef MyClass
    properties %   
        a
        b
    end
    methods % 
        function Y=MyClass() %  . 
        %    () Y   a, b
            Y.a=5;
            Y.b=10;
        end
    end
    methods (Static) %  
        function y=f(x) %  
            y=x^2; %    ,    !11
        end
    end
end


Essa. você entendeu tudo corretamente: os métodos são apenas estáticos, o construtor xs é para quê (está escrito na documentação - Oh, as classes devem ter um construtor - bem, aqui está um construtor para você), tudo o mais eu estupidamente não sabia e decidi que conhecia Zen e OOP.



Mas, no entanto, me pareceu uma ideia legal coletar funções (métodos estáticos) por classes-pastas, uma vez que havia muitos deles, e sentei-me para escrever um código de merda.



Burocracia



E topou com tal coisa. Existe um conjunto de funções de algum nível inferior de lógica (elas são estáticas e são compactadas em classes-pastas, agora omitiremos os nomes das classes):




Y1=f1(X1);
Y2=f2(X2);
Y3=f2(X3);
Y20=f20(X20);


Em pequenos projetos, é impossível alcançar tal domínio de funções, os exemplos educacionais geralmente contêm 2-3 funções - como "veja como podemos construir um PARABOL".



E aqui - a porra de uma nuvem de funções, e cada um deles, sua mãe, cada um tem um argumento de saída, e o que fazer com todos eles? Coloque funções de um nível lógico superior ("líder")! Normalmente, há muito menos deles (convencionalmente, 5 em vez de 20). Essa. convencionalmente, você precisa de alguma forma pegar estes Y1, Y2, Y3… .Y20 e REMOVÊ-los em alguns Z1, Z2… Z5. Para que depois você possa fazer uma reunião da festa e para ela:




A1=g1(Z1);
A2=g2(Z2);
A5=g5(Z5);
% ,  .  , !


Mas Z1 ... Z5 não vem sozinho. Para criá-los, você precisa de FUNCTIONS-WRAPPERS. Convencionalmente, eles funcionam mais ou menos assim ...




function Z1=Repack1(Y1,Y7, Y19)
    Z1.a=Y1.a+Y7.b*Y.19.e^2;
    Z1.b=Y7.c-Y19.e;
    %....  -      Y1, Y7, Y19 
    %    Z1. 
    %        Z2…Z5, 
    % 4 .  !
end


E então pode haver outro nível de "gestão" ...



Resumindo, percebi que estava em um inferno logístico. Eu normalmente não poderia extrair dados de uma FIGURE CLOUD de pequenas funções y = f (x) sem escrever outra FIGURE CLOUD funções burocráticas de reempacotamento e, quando os dados são transferidos para um nível superior, precisamos de mais RAPPERS. O programa final está abarrotado de burocracia por completo - há mais reembaladores do que "código de negócios". As classes de pasta de funções não resolvem esse problema - elas apenas reúnem os empacotadores burocráticos idiotas em pilhas.



E aí resolvi modernizar esse código de merda, e descobri que sem serrar toda a parte burocrática isso é impossível!



Assim como a vida na Rússia ...



Percebi que estava fazendo algo errado e entendi melhor o OOP. E a solução - se você olhar desta forma, foi ideologicamente na superfície.



Ideia OOP



Por que fazer um monte de funções como y = f (x) que produzem argumentos de saída DIFERENTES Y1… .Y20 , quando você pode fazer UM argumento. Tipo de:




Y_all=f1(Y_all, X1); 
Y_all=f2(Y_all, X2);
….
Y_all=f20(Y_all, X20);


Então, absolutamente todos os resultados da função serão colocados em uma estrutura, em um array, apenas em seus diferentes compartimentos. Tudo. Então Y_all pode ser transferido diretamente para o topo, para o nível superior da "administração".




Y_all=DO_MOST_IMPORTANT_SHIT(Y_all, options_how_to_do_this_shit)


Todas-todas-todas as funções-SEALERS-BUREAUERS vão para o traseiro! Todos os dados são coletados em UMA base Y_all , todas as funções de baixo nível colocam os frutos de seus trabalhos em diferentes compartimentos Y_all , o “gerenciamento” percorre todos os compartimentos Y_all e faz o que deve fazer. Nada supérfluo, o código é escrito rapidamente e funciona muito bem ...



Essa é exatamente a ideia do OOP e consiste. Nos livros didáticos, eles escrevem exemplos educacionais sobre maçãs e peras e, em seguida, mostram um programa em 5 linhas. Não há necessidade de qualquer OOP, nos exemplos para 5 linhas, uma vez que a transferência de dados para o "nível de gestão superior" é feita diretamente e sem problemas.



OOP é necessário quando um grande projeto é o problema de "burocratização" ....

Mas de volta ao ponto. Na OOP real, existe o SYNTAX SUGAR. O exemplo acima com Y_all usado apenas estruturas, funções f (,,,) serão consideradas estáticas. OOP é um conjunto de açúcar quando o código começa a ficar assim:




Y_all.f1(X1); %   Y_all=f1(Y_all, X1), 
Y_all.f2(X2); 
….
Y_all.f20(X20);
Y_all.DO_MOST_IMPORTANT_SHIT(options_how_to_do_this_shit);


Essa. decidimos colocar uma sintaxe confusa na qual você não pode escrever Y_all 2 vezes, mas faça isso apenas 1 vez. Pois a repetição é a mãe da gagueira.



O resto da explicação "como OOP funciona" resume-se a explicar como funciona o açúcar sintático.



Como funciona o açúcar sintático OOP



Primeiro, esse banco de dados Y_all , obviamente, precisa ser criado antes de ir como um argumento para a função. Isso requer um construtor.



Em segundo lugar, é aconselhável prever, de preferência com antecedência, quais são os "compartimentos" que terá. Contanto que o banco de dados Y_all seja pequeno, essa configuração é irritante. Eu gostaria de sonhar com "classes criadas na hora", da mesma forma que no MATLAB você pode fazer estruturas com comandos simples Ya = 5 , Yb = 10 . Mas o desejo de fantasiar sobre este tópico desaparece após depurar um projeto saudável.



Em seguida - chamando o método (função).



É assim que evoluiu aproximadamente

Função Comente
Y = f (X) Esse era o caso da matemática quando traçamos um PARABOL por pontos!
X = f (X) Fomos intimidados por burocratas e temos um lote de um argumento para todas as ocasiões, armazenando todos os dados de entrada e saída em diferentes compartimentos dentro
f (X) Por que uma função deve retornar um argumento? Esse é o arcaísmo dos tempos das aulas de matemática! E um desperdício de memória inútil! Se os dados forem passados ​​por referência, a própria função virá para o argumento, mudará e sairá. NADA = f (X)

Não a montanha vai para Maomé, mas Maomé para a montanha.
X.f () Acabamos de retirar o argumento X com açúcar sintático. NADA = X.f (NADA)




Agora - como é uma função internamente organizada que leva NADA e retorna NADA (a palavra-chave void em C #).



Gosto de como isso é feito no matlab (do ponto de vista do entendimento): a função que chamamos de Xf () é escrita internamente como

Amostra de código MATLAB Amostra de código C #

function f(this)
    % . 
    this.c=this.a+this.b;
end	



public void f() {
    this.c=this.a+this.b;
}


« » . — ( , this, fuck, shit).

this, .

« » . , ( )!

! , « this». «» this ( ).





Aqui está uma função com "o argumento padrão é este ", situada na classe, como em uma pasta - há um método comum (xs, como está correto em russo).

Na verdade, enchendo todos os argumentos em um único este nem sempre é correta. Às vezes, você precisa de alguns outros argumentos (por exemplo, esta é a entrada do usuário):




public void f(int user_input) {
    this.c=this.a+this.b + user_input;
}


Às vezes, você precisa até retornar um argumento (por exemplo, sobre o sucesso ou fracasso de uma operação) e não escrever void . O que, no entanto, não altera as estatísticas: a maioria das funções OOP não retorna NADA ( vazio ) e não aceita nada (o argumento padrão não conta) ou muito poucos argumentos.



Vamos escrever o código final

em MATLAB




classdef MyClass<handle %  handle      
    properties %   
        a
        b
    end
    methods % 
        function this=MyClass(a, b) %  . a, b -  
            this.a=a
            this.b=b
        end
        function f(this)
            this.c=this.a+this.b
        end
    end
end
%  -  Untitled.m 
X=MyClass(5,10);
X.f();
fprintf(‘X.c=%d',X.c) % .=15


Agora em C #:




public class MyClass {
    public int a;
    public int b;
    public MyClass(int a, int b) { //  . a, b -  ()		
        this.a=a;
        this.b=b;
    }
    public void f(this) {
        this.c=this.a+this.b
    }
}
//  -  
MyClass X=new MyClass(5,10);
X.f();
Console.WriteLine(“X.c={0}”,X.c);  // .=15


Quando eu descobri, parecia que a maioria dos problemas com a escrita de código haviam desaparecido em segundo plano ...



Propriedades vs campos



Vejamos um exemplo.

sem propriedades com propriedades

MyClassA{
    int a; // field ()

    public int Get_a(){
        return this.a;
    }    
     
    public void Set_a(int value){ 
    //   - 
    //, ,  value>0
        if (value>0) this.a=value;
        else this.a=0; 
    }
}



MyClassA{
    int a; // field ()

    public int A{
       get{return this.a;}
       set{ 
           if (value>0) 
               this.a=value;
           else 
               this.a=0; 
           }
    }
}



MyClass X=new MyClassA();
X.Set_a(5);
int b=X.Get_a();



MyClass X=new MyClassA();
X.A=5;
int b=X.A;


comentário: o argumento

Set_a pode ser chamado de qualquer

Set_a (int YourVarName)

comentário: uma variável dentro do

conjunto {...} deve sempre ser chamada de valor



Esta coisa é bastante conveniente e frequentemente usada, mas ainda é SYNTAX SUGAR.

O campo é uma variável totalmente qualificada. A propriedade consiste em 2 métodos de classe (get e set), a sintaxe de chamada que copia a "chamada de variável".



Na verdade, dentro de get e set, você pode fazer uma merda:




int A {
    get{ return 0;}
    set{ Console.WriteLine(""); }
}


Portanto, parece que é recomendado escrever as propriedades do nome com uma letra maiúscula e os campos com uma letra minúscula.



Acontece (por exemplo, você não pode criar um campo em interfaces) que você precisa fazer uma propriedade rapidamente, então você pode:




int A { get; set;} //  , -  _a
// set  get     .
public int B { get; private set;} //    
//(  ,      )


Herança, encapsulamento, polimorfismo



Por que você não os mencionou antes? Porque

- na verdade, ao escrever código, eles não são solicitados com tanta força como são mencionados na consulta "Ok Google, o que é OOP". Eu até diria que no começo eles são praticamente desnecessários pra caralho .

- onde eles são necessários, você pode ler sobre eles (apenas os preguiçosos não escreveram sobre este caso).
Quando há um processo de dominar as habilidades de escrita no estilo OOP

- você terá a maioria das classes SEM herança. Você acabou de escrever TODAS as funcionalidades na classe necessária, e você realmente não precisa herdar algo.

- consequentemente, o polimorfismo (uma loção para a herança) também atravessa a floresta

- "encapsulamento" é reduzido para atribuir público em todos os lugares (para todos os campos, propriedades e métodos).

Então, suas mãos crescerão até os ombros e você mesmo descobrirá, sem este artigo , onde NÃO deve fazer isso, especialmente onde NÃO deve escrever em público.



Mas ainda uma breve visão geral deles.



Herança. Este é um copiar-colar inteligente



Uma implementação falha de "herança" tem a seguinte aparência:

Oh, meu código de merda tem uma classe chamada MyClass, e está faltando outro campo SHIT e outro método DO_THE_SHIT ()!

* Ctrl + C, Ctrl + V

* Uma nova classe MyClass_s_fichami é criada e o desejado é adicionado lá

Mesmo assim, somos pessoas mais civilizadas e sabemos que é melhor não copiar o texto do programa, mas fazer referência a ele.



Digamos que ainda escrevemos em alguma linguagem de programação antiga ou não estamos cientes de algo como "herança". Em seguida, escrevemos 2 classes diferentes


public class MyClassA{ 
    public int a;
    public void F1(int x){
    //   
        this.a=this.a*3;
    }
    public MyClassA(int a){ //
        this.a=a;
    }
}



public class MyClassB { 
    //
    private  MyClassA fieldA;
    // get  set     
    // a - .. property
    public int a{ 
        get { return fieldA.a; }
        set { this.fieldA.a=value; }
    }
    public int b;
    //   
    // «»
    public void F1(int x){ 
       this.fieldA.F1();
    }
    public void F2(int x){
        //  
        this.b=this.a*this.b;
    }
    //
    public MyClassB(int a, int b){ 
        this.fieldA= new MyClassA();
        this.a=a;
        this.b=b;
    }
}



//-   
var X=new MyClassA(5);
X.F1(); // X.a   15
Console.WriteLine(X.a); // 15	



//-   
var X=new MyClassB(5,10);
X.F1();// X.a   5*3=15
X.F2();// X.b   15*10=150
Console.WriteLine(X.a); // 15
Console.WriteLine(X.b); // 150




O que fizemos à direita é herança. Apenas em linguagens de programação normais, isso é feito com um comando:




public class MyClassB : MyClassA { 
    //    MyClassA  , 
    //      base
    
    // a (, , property a)  , 
    //      (.    )
    public int b;
    public void F2(int x){ //  
        this.b=this.a*this.b;
    }
    public MyClassB(int a, int b){ //
    //   base    A 
    //     
        this.a=a;
        this.b=b;
    }
}


O código funciona "fora" exatamente da mesma maneira que na opção 2. Ou seja. o objeto, por assim dizer, torna-se um "matryoshka" - dentro de um objeto outro objeto fica estupidamente posicionado, e há "canais de comunicação", puxando os quais, você pode se referir ao objeto interno diretamente.



Cabeçalho de spoiler
image



No matlab, a situação é um pouco mais interessante. Quando você executa o construtor filho, MyClassB , não há uma chamada silenciosa para o construtor ancestral MyClassA .



Você precisa criá-lo diretamente. Por um lado, isso é irritante:




classdef MyClassB<MyClassA
    % ... 
    function MyClassB(a, b)
        this@MyClassA(a); %   ,   «»
        this.b=b;
    end
end


Mas se o descendente for chamado com outros argumentos, como MyClassB (d) , então você pode fazer uma conversão interna, algo como:




classdef MyClassB<MyClassA
    % ... 
    function MyClassB(d)
        a=d-5;
        this@MyClassA(a); 
        this.b=d+10;
    end
end


Em C #, isso não pode ser feito diretamente, e isso dá origem à necessidade de escrever algum tipo de "funções de conversão":




class MyClassB:MyClassA{
    //...  
    static int TransformArgs( int d) {return d-5;}
    MyClassB(int d):base(TransformArgs(d)) {this.b=d+10;}
}


ou faça "construtores estáticos" como este:




class MyClassB:MyClassA {
    //...  
    MyClassB(){} //    
    static MyClassB GetMyClassB(int d) {
        var X=new MyClassB(); //    
        //   
        .a=d-5;
        .b=d+10;
        return X;
    }
}


Parece sobre herança, basicamente tudo.



Naturalmente, ninguém forças herdeiro para escrever o " F1 " método ea " uma " propriedade de modo que eles são necessariamente traduzido para a chamada de método e campos do antepassado. A transmissão é apenas o comportamento de "herança" padrão. Você pode (é claro! Esses são outros métodos em outra classe, mano) escrever assim:








public class MyClassB : MyClassA {
    public int a{ //   
        get { return 0; }
        set { base.a=0; }//    this.fieldA.a=0;
    }
    public int b;
    public void F1(int x){ //     «»
        //   - base -  
        Console.WriteLine(“”);//     
    }
}


Encapsulamento



... Conceitualmente, isso significa que dentro de um objeto da classe MyClassB, um objeto da classe MyClassA fica no campo base, com a capacidade de transmitir comandos de controle para fora. Tudo isso está escrito acima e não faz sentido repetir.



Existe tal tópico com diferentes modificadores de acesso - público , privado , protegido ... Sobre eles, o que é mais interessante, está escrito em todo lugar mais ou menos normalmente, recomendo apenas ler sobre ele.

public - significa que o campo , propriedade ou método será visível de fora e pode ser puxado.

Se você NÃO sabe o que fazer, escreva publicamente (conselhos ruins, sim).



Em seguida, encontre a força em você mesmo e jogue fora esse público (ou, para clareza, substitua-o por privado ) sempre que for desnecessário (faça uma "refatoração"). Sim, claro, é muito bom ser um visionário, agir na batalha dos médiuns e adivinhar imediatamente onde fazer particular .

privado - isso significa que o campo , propriedade ou método de um objeto "pasta de arquivos" é visível apenas nos métodos desta classe.

MAS ... É uma classe, não uma INSTÂNCIA (objeto). Se você tiver um código como:




class MyClassA{
    private int a=10;
    public void DO_SOMETHING(MyClassA other_obj) { 
    // DO_SOMETHING          
    //  private      MyClassA.
        this.a=100; //    
        other_obj.a=100; //  
    }
}
var X=new MyClassA();
var Y=new MyClassA();
X.DO_SOMETHING(Y);  //  X.a=100, Y.a=100


Tal coisa é usada na clonagem (veja outras fontes para mais detalhes).



Tentei pensar sobre esse arranjo de público e privado ao escrever código . Este é um código inaceitavelmente demorado ao fazer o rascunho do código. E então descobrimos que o próprio código precisa ser feito de uma maneira fundamentalmente diferente.



Se o código for escrito em solo, então não faz sentido se preocupar com privado e público antes do tempo, há tarefas mais importantes, por exemplo, realmente criar e escrever o código ...

O único lugar onde é mais ou menos claro em que lugar colocar privado e público são os mesmos propriedades notórias que se referem a algum tipo de campo.




class MyClassA{
    //  private
    private int a; //"private"  C#     .
    //   public
    public int A {get{...;} set{...;}} //   ""
}


Em outros lugares, para organizar o público e o privado, você precisa realmente observar o que o programa está fazendo, e muito provavelmente não funcionará aprender isso "à revelia".

protegido - significa " público " para todos os métodos das classes derivadas e " privado " para todo o resto.

Em geral, é lógico assumirmos que as classes herdadas aparecem simplesmente como "versões mais sofisticadas" de seus ancestrais.



Sinceramente, já esqueci onde apliquei explicitamente essa proteção. Normalmente, é público ou privado. A maioria das classes que escrevi não herdou de nenhuma outra classe personalizada e, quando o fazia, raramente havia uma necessidade séria de tais coisas.



A impressão é que modificadores não públicos são necessários quando se trabalha em algum projeto grande, que pode ser apoiado por um monte de gente ... A compreensão de onde aplicá-los só aparece depois de muito tempo se apegando a um código de quilômetros de extensão. Ao estudar “por correspondência” é difícil de alguma forma dar esse entendimento.



Polimorfismo



Quando eu estava escrevendo em Matlab, eu não conseguia entender porque o polimorfismo é necessário e O QUE É.

Então, quando mudei para C #, percebi que esse é um recurso de IDIOMAS ESTRITAMENTE TÍPICOS e tem uma relação muito fraca com OOP. No matlab, você pode escrever em qualquer lugar sem saber sobre a existência desse polimorfismo - não há uma digitação estrita.



Para simplificar, deixe as classes serem chamadas de A e B




class A{...}
class B:A{...}
A X=new B();
//  x  A,   -   B. 
//   .
B x_asB=new B();
A x_asA=(A) x_asB;


Isso é chamado de typecasting. Em C #, você pode VOCÊ MESMO (se souber como) escrever seus próprios sistemas de fundição de tipo feitos por você mesmo, de quase qualquer tipo para qualquer outro.

Aqui - apenas "lançando" fora da caixa. Visto que outro objeto da classe A fica dentro de um objeto x pertencente à classe B , então uma das maneiras aparentemente óbvias de lançar é fechar todas as conexões de um objeto externo para um interno. Não é realmente necessário fazer isso, mas aqueles que inventaram o "polimorfismo" decidiram que seria mais óbvio fazer isso. E o próprio usuário escreverá o resto das opções. Desculpe pela (não mais relevante) "politota" da amostra de 2008-2012.










lass  {...}
class  :  {...} 
  = new Me (); //   
  = () ; //    


Interface



Devemos começar com como aplicar ISTO.



Digamos que temos uma lista e queremos incluir algo nela.



No matlab, a maneira mais fácil de fazer isso é (chamada de array de células):




myList={1, ‘2’, ‘fuck’, ‘shit’, MyClassA(), MyClassB(), …. ,_, _};


Você não pensa que tipo de objeto é, apenas pega e coloca na lista.



Em seguida, digamos que você precise percorrer a lista e fazer algo com cada elemento:




for i=1:length(myList)
      item=myList(i);
      %   -   item-
      DoSomeStuff(item);
end


Se a função DoSomeStuff for inteligente o suficiente para digerir tudo o que for alimentado a ela, este código SERÁ CUMPRIDO.



Se a função DoSomeStuff (ou seu autor) não brilhar com inteligência, então existe a possibilidade de engasgar com algo: um número, uma linha, sua aula feita por você mesmo, o Diabo Careca ou - Deus me livre - sua avó.



O MATLAB mostrará palavrões vermelhos em inglês no console e encerrará seu programa. Assim, seu código receberá automaticamente um Prêmio Darwin.



No entanto, isso é realmente ruim, porque às vezes o código é muito complexo. Então, você ficará firmemente convencido de que fez tudo corretamente, mas, na verdade, a combinação errada de ações simplesmente nunca foi iniciada durante o teste.



É por isso (embora não apenas porque) no MATLAB - eu mesmo consegui ter certeza disso (aproximadamente como no KPDV), no tamanho terrível do código - NÃO HÁ NECESSIDADE de escrever grandes projetos.



Agora vamos passar para o C #. Fazemos uma lista e ... e somos solicitados a indicar imediatamente o TIPO do objeto. Criamos uma lista do tipo Lista.



Nessa lista, você pode colocar o número 1.



Em tal lista, você pode colocar o número 2 e até mesmo, Deus me perdoe, 3.




List<int> lst1=new List<int>().
lst.Add(1);
lst.Add(2);
lst.Add(3);


Mas as strings de texto não estão mais lá. Objetos de sua classe self-made - estritamente não. Silêncio sobre o Diabo Careca e sua avó, eles não podem estar lá sob nenhuma variante.



Você pode fazer uma lista separada de linhas. Você pode - para suas aulas feitas por você mesmo.




List<MyClassA> lst2=new List<MyClassA>();
lst2.Add(new MyClassA());


Na verdade, você pode fazer listas - separadamente - de Bald Devils, suas avós.



Mas adicioná-los a uma lista não funcionará. Seu código ganhará um Prêmio Darwin, combinado com abuso do compilador antes mesmo de tentar executá-lo. O compilador prudentemente não permite que você faça a função DoSomeStuff (item) , que irá "engasgar" com seu argumento.



Isso é realmente útil em grandes projetos.

Mas o que fazer quando você ainda quer colocá-lo em uma pequena lista?



Isso não é realmente um problema. Basta converter tudo em objeto de tipo . Quase (ou mesmo absolutamente) tudo pode ser convertido em objeto de tipo .




List<object> lst=new List<object>();
lst.Add((object) new MyClassA());
lst.Add((object) new MyClassB());


O problema começa quando começamos a percorrer a lista. A questão é que o tipo de objeto (quase) não pode fazer nada. Só pode ser do tipo objeto .

- O que você pode fazer?

- Eu sei cantar e dançar

- E eu - Sancho ...

- O que você pode fazer, Sancho?

- Eu sou Sancho.

- Bem, você pode fazer alguma coisa?

- Você não entende. Eu posso ser Sancho.



Portanto, a interface está escrita. Esta é a classe da qual herdar. A interface contém cabeçalhos de método e propriedade.



No nosso caso, são os métodos e propriedades que garantem o funcionamento NORMAL da função DoSomeStuff (item) . A interface não implementa as próprias propriedades. Isso é feito de propósito. Na verdade, pode-se simplesmente herdar de alguma classe que pode ser usada pela função DoSomeStuff () . Mas isso significa código extra e um programador esquecido.



Portanto, se um colega programador herdou de uma interface, mas se esqueceu de implementar as propriedades e métodos necessários de uma classe, o compilador irá escrevê-lo no código com um Prêmio Darwin. Assim, você pode fazer isso:




interface ICanDoTheStuff {...};
class MyClassA: ICanDoTheStuff {…}
class MyClassB: ICanDoTheStuff {…}
static void DoSomeStuff(ICanDoTheStuff item) {…}

List<ICanDoTheStuff> lst= new List<ICanDoTheStuff>();
lst.Add(new MyClassA());
lst.Add(new MyClassB());

for (int i=0; i<lst.Count; i++) {
      ICanDoTheStuff item=myList[i];
      DoSomeStuff(item);
}


Essa. para o qual, no final, uma interface é necessária - para fazer uma lista digitada, ou algum campo na classe, e contornar a proibição de adicionar (à lista ou campo) algum lixo deixado ali.



A interface é a "burocracia". Não está em todos os lugares e não é necessário em todos os lugares, embora sim, seja necessário e útil em grandes projetos.



... em geral, algo assim ... Peço desculpas pelas expressões ásperas, por algum motivo me parece que uma apresentação "seca" do material não teria sucesso ...



All Articles