El camino hacia la programación orientada a objetos: la perspectiva de un ingeniero

Descargo de responsabilidad



El artículo no implica ninguna perspectiva fundamentalmente nueva de las cosas, excepto desde el punto de vista de estudiar este material desde el "cero absoluto".





El material se basa en notas de hace unos 7 años, cuando apenas comenzaba mi camino para estudiar POO sin educación en TI. En aquellos días, MATLAB era el lenguaje principal, mucho después cambié a C #.



La presentación de los principios de OOP, que encontré, con ejemplos en forma de algunas manzanas, peras heredadas de la clase "fruta" y un montón de terminología (herencia, polimorfismo, encapsulación, etc.), se percibió como una letra china.



Por el contrario, ahora, por alguna razón, percibo tal material normalmente, y la presentación de mi propio artículo a veces parece confusa y larga.



Pero mis viejas notas y el horrible código superviviente de los holodiscos en el pipboy indican que la presentación "clásica" no cumplió con sus funciones en ese momento, y fue completamente infructuosa. Quizás haya algo en esto.



Cuánto corresponde esto a la realidad y a sus propias preferencias, decida usted mismo ...



Condiciones previas para la programación orientada a objetos



Código de pared



Cuando comencé a escribir en MATLAB'e, era la única forma de escribir y sabía cómo hacerlo. Sabía de funciones y que el programa se puede dividir en partes.



El problema era que todos los ejemplos apestaban. Abrí el libro del curso de alguien, vi allí pequeñas funciones corporales de 2-3 líneas, en total todo esto NO funcionó (faltaba algo), y solo funcionó cuando volví a ensamblar esta basura en una "pared".



Luego escribí algunos programas pequeños varias veces, y cada vez me preguntaba por qué había algo que compartir. Sólo más tarde llegó el entendimiento: el código "muro" es el estado normal de un programa de aproximadamente 1,5 páginas A4. No hay funciones y, Dios no lo quiera, NO se necesita OOP allí.



Así es como se ve el script de Matlab (tomado de 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)|')


División de código en funciones



Por qué el código todavía se está dividiendo en partes, supuse cuando su volumen comenzó a volverse completamente inimaginable (ahora encontré un código de mierda en el archivo: 650 líneas junto a una pared). Y luego me acordé de las funciones. Sabía que te permitían dividir tu código en pequeños bloques que son más fáciles de depurar y reutilizar.



Pero el truco es diferente: por alguna razón, todos los materiales de enseñanza no dicen nada acerca de CUÁNTO tiene una función de variables ...



El curso de matemáticas dice que una función es y = f (x)



. Por ejemplo, y = x 2 es un PARABOLO completo.

Problema matemático: construye un PARABOL por puntos. En una hoja de cuaderno, en una caja.

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









, « », . , , . – . .



-…



imagen


Y si la función tiene cuatro o más variables…. Teoría de supercuerdas. Variedad Calabi-Yau. Mortal. No dado. Comprenda ...



En resumen, todo esto está mal. En programación, el estado normal de una función es doble vaginal doble anal . Toma 100 variables y devuelve lo mismo, lo cual está bien. Otra cosa es anormal: enumerarlos con una COMA.



imagen


Sobre el hecho de que puedes escribir de alguna manera diferente, me di cuenta cuando naval AQUÍ ESTO




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


Un montón de variables separadas por COMMA. Y el código de llamada tiene nombres completamente diferentes para estos parámetros, algo así como SelectFun (a, b, c, d….) Por lo tanto, debe recordar dónde está qué variable. Y hacer su arreglo a través de COMMA. Y si el código se está modernizando y la cantidad de variables cambia, entonces deben reorganizarse nuevamente con una COMMA.



¿Y por qué había variables globales (¡dispara!) En esta miseria?



¡Bingo! Para no ordenar las variables con cada código, actualice a través de COMMA.



Pero la COMMA todavía me seguía como en una pesadilla.



imagen


Y apareció varargin. Esto significa que puedo agregar muchos más argumentos en el código de llamada con COMMA ...



Y luego pensé en matrices. Los ejemplos de tutoriales hablaron con entusiasmo sobre el hecho de que una matriz puede ser así:




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


Y ve, X (2,3) = 6, y X (3,3) = 9, y nosotros ... ¡podemos organizar la multiplicación de matrices en tales matrices! En la última lección pasamos por PARABOLS, y ahora MATRIXES….



Y ni una sola línea de estos malditos libros de texto es corta y clara: necesitas matrices para hacer una función de 100 variables y no caer de su lista a través de una COMA.



imagen


En general, se me ocurrió la idea de meter todo en una gran mesa bidimensional. Todo salió bien al principio:




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)


Pero yo quería más. Y empezó a verse así:




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)


Y todo parece ir bien, pero ... ¡CERO! Aparecieron porque quería esparcir datos heterogéneos en diferentes líneas, y la cantidad de datos de diferentes tipos era diferente ... ¿Y cómo debería la función procesar estos ceros? ¿Qué pasa si quiero actualizar el código? ¡Tendré que reescribir el controlador de estos desagradables ceros dentro de la función! Después de todo, algunas de las variables en realidad pueden ser iguales a cero ...



Nunca pedí esto ...



En general, así es como aprendí sobre ESTRUCTURAS.



Estructuras



Aquí es donde fue necesario comenzar la presentación sobre métodos de empaquetado de datos. Las matrices con una "tabla", aparentemente, surgieron históricamente primero, y también escriben sobre ellas, al principio. En la práctica, puede encontrar una gran cantidad de programas en los que las matrices como una "tabla" son unidimensionales o están ausentes.

La estructura es un paquete de datos de "carpeta de archivos", aproximadamente en el disco duro de una computadora.

Unidad D: \

X (carpeta-variable - "objeto" o "estructura")

- a.txt (archivo variable con datos - "campo del objeto", campo en inglés.

Se almacena el número 5) - b.txt (se almacena el número 10 )

- .txt

Y (subcarpeta variable - "objeto")

- d.txt (se almacena el número 2)

- e.txt



Para hacerlo más claro, anotemos cómo veríamos la ruta al archivo d.txt en el Explorador de Windows

D: \ X \ Y \ d.txt


Después de eso, abrimos el archivo y escribimos el número "2" allí.

Ahora, cómo se verá en el código del programa. No es necesario hacer referencia a la "unidad raíz local", por lo que D: \ simplemente no está allí, ni tendremos una extensión de archivo. En cuanto al resto, generalmente se usa un punto en lugar de una barra \ en la programación.

Resulta así:




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


En matlab, las estructuras ( struct ) se pueden crear en el acto, sin salir de la caja, es decir, el código anterior es ejecutable, puede conducirlo a la consola y todo funcionará de inmediato. La estructura aparecerá inmediatamente, y todos los "archivos-variables" y "subcarpetas-variables" se agregarán allí a la vez. Desafortunadamente, es imposible decirlo acerca de C #, la estructura ( estructura ) la establecen las hemorroides.



La estructura es un pariente más fresco de TABLE ARRAY, donde en lugar de índices, un sistema de carpetas de archivos. Estructura = "carpeta-variable", que contiene "archivos-variables" y otras "carpetas-variables" (es decir, una especie de subcarpetas).



Todo es familiar, todo es exactamente igual que en una computadora, carpetas, archivos en ellos, solo en los archivos, no en las imágenes, sino en los números (aunque las imágenes también son posibles).



Esta es una versión más avanzada de almacenar datos para pasar a una FUNCIÓN en comparación con la idea de hacer una TABLA DE ARRAYES, especialmente bidimensional y, me molesta, un tesseract, tridimensional y más.

La TABLA DE ARRAY se puede utilizar en dos casos:

- es pequeña (¿por qué entonces? ¿Qué, no se pueden pasar argumentos separados por comas a la función?).

- o puede hacer un bucle y automatizar la búsqueda / llenado (esto no siempre es posible)

En realidad, ARRAY TABLE generalmente se usa solo como una fila unidimensional de datos homogéneos. Todo lo demás en los programas normales se realiza de acuerdo con el esquema de "carpeta de archivos".



Entonces, ¿por qué los libros de texto de programación comienzan con matrices y tablas?



En resumen, "habiendo descubierto" las estructuras por mí mismo, decidí que había encontrado una mina de oro y reescribí todo urgentemente. El código de mierda se veía así:




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)


Sí, puedes hacer perfeccionismo aquí y hacer un montón de objetos anidados, pero ese no es el punto. Lo principal es que ahora, dentro de la función, la variable está indexada no por su número ordinal (donde está en la lista de argumentos, separada por COMA), sino por su nombre. Y no hay ceros tontos. Y la llamada a la función ahora es de una forma aceptable, solo hay 2 COMANDOS, puede exhalar con calma.



Clases



El concepto de "clase" me dejó un montón de terminología: encapsulación, herencia, polimorfismo, métodos estáticos, campos, propiedades, métodos ordinarios, constructor ... # @% !!! ..

Por inexperiencia, habiendo descubierto las estructuras, decidí que no había necesidad de complicar las entidades innecesariamente, y pensó: "las clases son como las mismas estructuras, solo que más complicadas".



Hasta cierto punto, es así. Más precisamente, esto es exactamente lo que es. Una clase, si miras muy profundamente, es una ESTRUCTURA (un descendiente ideológico de una matriz por una tabla), que se crea cuando el programa está INICIADO (en general, parece serlo, y no solo al inicio). Como en cualquier descendiente de ARRAY TABLE, los datos se almacenan allí. Se puede acceder a ellos mientras se ejecuta el programa.



Por lo tanto, mi primera clase fue algo así (estoy escribiendo un ejemplo en C #, en matlab, los campos estáticos no se implementan normalmente, solo a través de una curva de corte con variables persistentes en una función estática).



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

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


El caso anterior es, por así decirlo, la habilidad "básica" de una clase: ser estúpidamente una matriz (estructura) con datos. Estos datos se agregan al inicio del programa, y ​​desde allí se pueden extraer, exactamente de la misma manera en que los sacamos de la estructura anterior. La palabra clave estática se utiliza para esto .



La estructura -> se crea en cualquier lugar y almacena los datos que se ingresan en ella en cualquier momento



La clase -> es la estructura que se crea cuando se inicia el programa. Todos los campos marcados con la palabra estática simplemente almacenan datos, como en una estructura normal. Los métodos estáticos son simplemente funciones que se llaman desde una clase, al igual que desde una carpeta.




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


Tuve una broma: si los campos son variables y los métodos son funciones, ¿cómo se almacenan en un solo lugar? Según tengo entendido, una función (método) en una clase en realidad no es una función, sino un puntero a una función. Aquellos. se trata de la misma "variable" que pi en términos de trabajar con ella.

En resumen, al principio entendí las clases exactamente en este volumen y escribí otra porción de código de mierda, donde SOLO se usaban funciones estáticas. De lo contrario, como carpeta con funciones, no usé clases en absoluto.



Este punto también se vio facilitado por el hecho de que así es exactamente como se hacen las clases en MATLAB, como una carpeta tan estúpida, cuyo nombre comienza con @ (como @ Math, sin un espacio), dentro de él, los archivos reales con la extensión .m son funciones (métodos) y hay archivo de encabezado con la extensión .m, que explica que la función CircleLength realmente pertenece a la clase, y no es solo un archivo .m con una función que no es OOP incluida allí.

Carpeta @ Math%

- Archivo de encabezado

Math.m% - Archivo de función CircleLength.m%

Sí, existe una forma más familiar para que una persona normal escriba una clase en un archivo .m, pero al principio no lo sabía. Los campos estáticos en matlab solo son constantes y se escriben una vez cuando se inicia el programa. Probablemente para protegerse contra el "arrastre", que decide asignar Math.pi = 4 (en mi humilde opinión, un tema absolutamente inútil y estúpido, ninguna persona normal escribirá un proyecto grande en matlab, y un programador depurará un proyecto pequeño y, por lo tanto, es poco probable el es un idiota).



Pero volvamos al tema. Además de los métodos estáticos, la clase también tiene un constructor. Un constructor es básicamente una función como y = f (x) o incluso y = f (). Puede que no tenga argumentos de entrada, debe tener una salida y esta es siempre una nueva estructura (matriz).



Qué hace el constructor. Simplemente hace estructuras. Lógicamente se ve así:



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();	






Mierda de código en matlab, creando estructuras similares sin clases (donde la clase está presente, ver más abajo):




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


Y en la salida tenemos la estructura

Y (variable de carpeta)

- a (variable de archivo, igual a 5)

- b (variable de archivo es igual a 10)

De esto, de hecho, queda claro que los llamados campos de clase (no estáticos, sin el código clave estático ) son variables locales declaradas dentro de la función constructora. El hecho de que estén escritos para algún tipo de diablo no en el constructor, sino fuera, es AZÚCAR SINTAXICO.



SYNTAX SUGAR: esas características de mierda de un lenguaje de programación, cuando el código comienza a parecer como si quisieran ofuscarlo justo cuando está escrito. Pero, por otro lado, se vuelve más corto y más rápido (supuestamente) escrito.



Habiendo hecho este "descubrimiento", yo, que en ese momento escribía solo en Matlab, estaba increíblemente sorprendido.



En matlab, como escribí anteriormente, estas estructuras se pueden crear en su lugar, sin ningún constructor, simplemente escribiendo Ya = 5 , Yb = 10, al igual que tú en el sistema operativo puedes crear archivos y carpetas sin salir de la caja registradora.



Y aquí, algún tipo de "constructor", y todos los campos de la estructura (en el matlab se llaman propiedades, propiedades, aunque, estrictamente hablando, las propiedades son algo más oscuro que los campos) deben escribirse burocráticamente en el archivo de encabezado. ¿Para qué? El único beneficio que vi en este sistema es que los campos de estructura están predefinidos, y esto es como una "auto-documentación": siempre puede ver lo que debería estar ahí y lo que no debería estar ahí. Aquí hay algo como esto que escribí entonces:




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


Aquellos. entendiste todo correctamente: los métodos son solo estáticos, el constructor xs es para qué (está escrito en la documentación - Oh, las clases deben tener un constructor - bueno, aquí hay un constructor para ti), todo lo demás estúpidamente no sabía y decidí que conocía Zen y OOP.



Sin embargo, me pareció una buena idea recopilar funciones (métodos estáticos) por clases-carpetas. había muchos de ellos, y me senté a escribir código de mierda.



Burocracia



Y me encontré con tal cosa. Hay un conjunto de funciones de algún nivel más bajo de lógica (son estáticas y están empaquetadas en clases-carpetas, ahora omitiremos los nombres de las clases):




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


En proyectos pequeños, es imposible lograr tal dominio de funciones, los ejemplos educativos generalmente contienen 2-3 funciones, como "ver cómo podemos construir un PARABOL".



Y aquí, una puta nube de funciones, y cada una de ellas, su madre, cada una tiene un argumento de salida, ¿y qué hacer con todas ellas? ¡Ponga funciones de un nivel de lógica superior ("principal")! Por lo general, hay muchos menos (convencionalmente, 5 en lugar de 20). Aquellos. condicionalmente, es necesario tomar de alguna manera estos Y1, Y2, Y3… .Y20 y RIPPED en algunos Z1, Z2… Z5. Para que luego puedas hacer una reunión de la fiesta y en ella:




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


Pero Z1… Z5 no vienen por sí mismos. Para crearlos, necesita FUNCTIONS-WRAPPERS. Convencionalmente, funcionan de esta manera ...




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


Y luego puede haber otro nivel de "gestión" ...



En resumen, me di cuenta de que estaba en un infierno logístico. Normalmente no podría extraer datos de una NUBE DE FIGURAS de funciones pequeñas y = f (x) sin escribir una NUBE DE FIGURAS de funciones burocráticas de reempaquetado, y cuando los datos se transfieren a un nivel superior, necesitamos más REEMPLAZADORES. El programa final está repleto de burocracia de principio a fin: hay más reempaquetadores que "código comercial". Las clases de carpetas para funciones no resuelven este problema, simplemente recolectan los reempaquetadores idiotas burocráticos en montones.



Y luego decidí modernizar este código de mierda, ¡y resultó que sin cortar toda la parte burocrática, esto es imposible!



Al igual que la vida en Rusia ...



Me di cuenta de que estaba haciendo algo mal y entendí mejor la programación orientada a objetos. Y la solución, si lo miras de esta manera, estaba ideológicamente en la superficie.



Idea de programación orientada a objetos



¿Por qué hacer un montón de funciones como y = f (x) que producen DIFERENTES argumentos de salida Y1… .Y20 , cuando puedes hacer UN argumento? Algo así como:




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


Entonces, absolutamente todos los resultados de las funciones se integrarán en una estructura, en una matriz, solo en sus diferentes compartimentos. Todos. Entonces Y_all se puede transferir directamente a la parte superior, al nivel superior de la "gestión".




Y_all=DO_MOST_IMPORTANT_SHIT(Y_all, options_how_to_do_this_shit)


Todas-todas-todas las funciones-SEALERS-BUREAUERS van al culo! Todos los datos se recopilan en la base ONE Y_all , todas las funciones de bajo nivel colocan los frutos de su trabajo en diferentes compartimentos Y_all , la "administración" corre a través de todos los compartimentos Y_all y hace lo que debe hacer. Nada superfluo, el código se escribe rápidamente y funciona muy bien ...



Esta es exactamente la idea de POO y consiste. En los libros de texto, escriben ejemplos educativos sobre manzanas y peras, y luego muestran un programa en 5 líneas. No hay necesidad de ningún POO en absoluto, en los ejemplos de 5 líneas, ya que la transferencia de datos al “nivel de alta dirección” se realiza directamente sin problemas.



La POO es necesaria cuando un gran proyecto es el problema de la "burocratización" ....

Pero volvamos al grano. En OOP real, hay SYNTAX SUGAR. El ejemplo anterior con Y_all usó solo estructuras, las funciones f (,,,) se considerarán estáticas. OOP es un conjunto de azúcar cuando el código comienza a verse así:




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);


Aquellos. Decidimos traer una sintaxis confusa en la que no se puede escribir Y_all 2 veces, pero hacerlo solo 1 vez. Porque la repetición es la madre de la tartamudez.



El resto de la explicación "cómo funciona OOP" se reduce a explicar cómo funciona el azúcar sintáctico.



Cómo funciona el azúcar sintáctico OOP



Primero, esta base de datos Y_all , obviamente, debe crearse antes de que vaya como argumento a la función. Esto requiere un constructor.



En segundo lugar, conviene prever, preferentemente con antelación, qué "compartimentos" tendrá. Siempre que la base de datos Y_all sea pequeña, esta configuración es molesta. Me gustaría soñar con "clases creadas sobre la marcha", de la misma manera que en MATLAB puedes crear estructuras con comandos simples Ya = 5 , Yb = 10 . Pero el deseo de fantasear con este tema desaparece tras depurar un proyecto saludable.



Siguiente: llamar al método (función).



Así es como evolucionó aproximadamente

Función Comentario
Y = f (X) ¡Este fue el caso en matemáticas cuando trazamos un PARABOL por puntos!
X = f (X) Fuimos intimidados por burócratas, y tenemos un lote de un argumento para todas las ocasiones, almacenando todos los datos de entrada y salida en diferentes compartimentos dentro
f (X) ¿Por qué una función debería devolver un argumento? ¡Este es el arcaísmo de los tiempos de las lecciones de matemáticas! ¡Y una pérdida de memoria inútil! Deje que los datos se pasen por referencia, luego la función en sí vendrá al argumento, cambiará y se irá. NADA = f (X)

No la montaña va a Mahoma, sino Mahoma a la montaña.
X.f () Acabamos de sacar el argumento X con azúcar sintáctico. NADA = X.f (NADA)




Ahora, ¿cómo se organiza internamente una función que no toma NADA y no devuelve NADA (la palabra clave void en C #).



Me gusta cómo se hace en matlab (desde el punto de vista de la comprensión): la función que llamamos como Xf () se escribe internamente como

Ejemplo de código MATLAB Ejemplo 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 ( ).





Aquí hay una función con "el argumento predeterminado es este ", que se encuentra en la clase, como en una carpeta; hay un método ordinario (xs, como está correctamente en ruso).

De hecho, no siempre es correcto meter todos los argumentos en un solo esto . A veces necesita otros argumentos (por ejemplo, esta es la entrada del usuario):




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


A veces, incluso necesita devolver un argumento (por ejemplo, sobre el éxito o el fracaso de una operación) y no escribir vacío . Lo que, sin embargo, no cambia las estadísticas: la mayoría de las funciones de programación orientada a objetos no devuelven NADA ( nulo ) y no aceptan nada (el argumento predeterminado no cuenta) o muy pocos argumentos.



Escribamos el código final

en 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


Ahora en 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


Cuando lo descubrí, parecía que la mayoría de los problemas con la escritura de código se desvanecían en el fondo ...



Propiedades vs campos



Veamos un ejemplo.

sin propiedades con propiedades

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;


comentario: el argumento

Set_a se puede llamar como sea

Set_a (int YourVarName)

comentario: una variable dentro del

conjunto {...} siempre debe llamarse valor



Esta cosa es bastante conveniente y de uso frecuente, pero sigue siendo SYNTAX SUGAR.

El campo es una variable completamente calificada. La propiedad es de 2 métodos de clase (get y set), cuya sintaxis de llamada copia "variable call".



De hecho, dentro de get and set, puedes hacer tonterías:




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


Por lo tanto, parece que se recomienda escribir las propiedades del nombre con una letra mayúscula y los campos con una letra minúscula.



Sucede (por ejemplo, no puede crear un campo en las interfaces) que necesita hacer la propiedad rápidamente, luego puede:




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


Herencia, encapsulación, polimorfismo



¿Por qué no los habías mencionado antes? Porque

, de hecho, al escribir código, no tienen tanta demanda como se menciona en la consulta "Ok Google, qué es OOP". Incluso diría que al principio son prácticamente jodidamente innecesarios .

- donde sean necesarios, puede leer sobre ellos (solo los vagos no escribieron sobre este caso).
Cuando hay un proceso de dominar las habilidades de escritura estilo OOP

- tendrás la mayoría de clases SIN herencia. Simplemente escribe TODA la funcionalidad en la clase requerida, y realmente no necesitas heredar algo.

- en consecuencia, el polimorfismo (una loción para la herencia) también pasa por el bosque

- la "encapsulación" se reduce a asignar público en todas partes (a todos los campos, propiedades y métodos).

Entonces sus manos crecerán hasta sus hombros y lo resolverá usted mismo, sin este artículo , donde NO debe hacer esto, especialmente donde NO debe escribir en público.



Pero sigue siendo una breve descripción de ellos.



Herencia. Esta es una inteligente copia y pegado



Una implementación defectuosa de "herencia" se ve así:

¡Oh, mi código de mierda tiene una clase llamada MyClass, y le falta un campo SHIT más y otro método DO_THE_SHIT ()!

* Ctrl + C, Ctrl + V

* Se crea una nueva clase MyClass_s_fichami y se agrega la deseada allí

Aún así, somos gente más civilizada, y sabemos que es mejor no copiar el texto del programa, sino hacer referencia a él.



Digamos que todavía escribimos en algún lenguaje de programación antiguo o que no somos conscientes de algo así como "herencia". Luego escribimos 2 clases 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




Lo que hicimos a la derecha es herencia. Solo en los lenguajes de programación normales esto se hace con un 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;
    }
}


El código funciona "afuera" exactamente de la misma manera que en la opción 2. Es decir. el objeto, por así decirlo, se convierte en una "muñeca de anidación": dentro de un objeto, otro objeto se sienta estúpidamente, y hay "canales de comunicación", tirando de los cuales, puede dirigirse directamente al objeto interno.



Encabezado de spoiler
image



En matlab, la situación es algo más interesante. Cuando ejecuta el constructor secundario, MyClassB , no hay una llamada silenciosa al constructor ancestro MyClassA .



Necesitas crearlo directamente. Por un lado, esto es molesto:




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


Pero si se llama al descendiente con otros argumentos, como MyClassB (d) , entonces puede hacer una conversión interna, algo como:




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


En C #, esto no se puede hacer directamente, y esto da lugar a la necesidad de escribir algún tipo de "funciones de conversión":




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


o hacer "constructores 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 una herencia, básicamente todo.



Naturalmente, nadie obliga al heredero a escribir el método " F1 " y la propiedad " a " de modo que necesariamente se traduzcan en la llamada del método y los campos del antepasado. La transmisión es solo el comportamiento de "herencia" predeterminado.



Puedes (¡por supuesto! Estos son otros métodos en otra clase, hermano) escribir así:




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(“”);//     
    }
}


Encapsulamiento



... Conceptualmente, esto significa que dentro de un objeto de clase MyClassB en el campo base se encuentra un objeto de clase MyClassA, con la capacidad de transmitir comandos de control al exterior. Todo esto se ha escrito anteriormente y no tiene sentido repetirlo.



Existe un tema de este tipo con diferentes modificadores de acceso: público , privado , protegido ... Sobre ellos, lo que es más interesante, está escrito en todas partes más o menos normalmente, recomiendo solo leer sobre él.

public : esto significará que el campo , la propiedad o el método serán visibles desde el exterior y se podrán extraer.

Si NO sabe qué hacer, escriba en público (mal consejo, sí).



Luego encuentre la fuerza en usted mismo y descarte este público (o, para mayor claridad, reemplácelo con privado ) donde sea innecesario (haga una "refactorización"). Sí, claro, es muy bueno ser un visionario, actuar en la batalla de los psíquicos y adivinar enseguida dónde hacer privado .

privado : esto significa que el campo , propiedad o método de un objeto "carpeta de archivos" es visible solo desde dentro de los métodos de esta clase.

PERO ... Es una clase, no una INSTANCIA (objeto). Si tiene un 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 cosa se usa en la clonación (consulte otras fuentes para obtener más detalles).



Traté de pensar en esta disposición de público y privado al escribir código . Este es un código que consume tiempo inaceptablemente cuando se hace un borrador del código. Y luego resulta que el código en sí debe hacerse de una manera fundamentalmente diferente.



Si el código está escrito en solitario, entonces no tiene sentido preocuparse por lo público y lo privado antes de tiempo, hay tareas más importantes, por ejemplo, realmente crear y escribir el código ... El único lugar donde está más o menos claro en qué lugar poner lo público y lo privado es lo mismo propiedades notorias que se refieren a algún tipo de campo.






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


En otros lugares, para organizar lo público y lo privado, debe fijarse realmente en lo que está haciendo el programa, y ​​lo más probable es que no funcione aprender esto "in absentia".

protegido : esto significa " público " para todos los métodos de las clases derivadas y " privado " para todo lo demás.

En general, es lógico si asumimos que las clases heredadas aparecen simplemente como "versiones más sofisticadas" de sus ancestros.



Honestamente, ya he olvidado dónde apliqué explícitamente esta protección. Suele ser público o privado. La mayoría de las clases que escribí no heredaron de ninguna otra clase personalizada, y donde lo hicieron, rara vez hubo una necesidad seria de tales cosas.



La impresión es que los modificadores no públicos están en demanda cuando se trabaja en un proyecto grande, que puede ser apoyado por un grupo de personas ... La comprensión de dónde aplicarlos aparece solo después de un largo tiempo de pegarse en un código de un kilómetro de largo. Cuando se estudia "por correspondencia" es difícil de alguna manera dar esta comprensión.



Polimorfismo



Cuando estaba escribiendo en Matlab, no podía entender por qué el polimorfismo es necesario y QUÉ ES.

Luego, cuando cambié a C #, me di cuenta de que esta es una característica de los IDIOMAS ESTRICTAMENTE TÍPICOS y que tiene una relación muy débil con la programación orientada a objetos. En matlab, puede escribir en todas partes sin conocer la existencia de este polimorfismo; no hay una mecanografía estricta.



Para simplificar, llamemos a las clases A y B




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


A esto se le llama encasillamiento. En C #, puede USTED MISMO (si sabe cómo) escribir sus propios sistemas de fundición de tipos personalizados, de casi cualquier tipo a cualquier otro.

Aquí, simplemente "echando" fuera de la caja. Dado que otro objeto de la clase A se encuentra dentro de un objeto x que pertenece a la clase B , entonces una de las formas aparentemente obvias de fundición es cerrar todas las conexiones de un objeto externo a uno interno. Esto no es realmente necesario, pero aquellos que inventaron el "polimorfismo" decidieron que sería más obvio hacerlo. Y el usuario escribirá el resto de las opciones él mismo. Perdón por la (ya no del todo relevante) "politota" de la muestra 2008-2012.










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


Interfaz



Debemos comenzar con cómo aplicar ESTO.



Digamos que tenemos una lista y queremos poner algo en ella.



En matlab, la forma más fácil de hacer esto es (llamada matriz de celdas):




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


No piensa qué tipo de objeto es, simplemente lo toma y lo pone en una lista.



A continuación, digamos que necesita recorrer la lista y hacer algo con cada elemento:




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


Si la función DoSomeStuff es lo suficientemente inteligente como para digerir lo que se le suministre, este código SE CUMPLIRÁ.



Si la función DoSomeStuff (o su autor) no brilla con inteligencia, entonces existe la posibilidad de atragantarse con algo: un número, una línea, tu clase hecha por ti mismo, el Diablo Calvo o, Dios no lo quiera, tu abuela.



MATLAB mostrará juramento rojo en inglés en la consola y finalizará su programa. Por lo tanto, su código recibirá automáticamente un premio Darwin.



Sin embargo, esto es realmente malo porque a veces el código es muy complejo. Entonces estará firmemente convencido de que hizo todo correctamente, pero de hecho, la combinación errónea de acciones simplemente nunca se lanzó durante la prueba.



Es por eso que (aunque no solo porque) en MATLAB, logré asegurarme de esto yo mismo (aproximadamente como en KPDV), en el terrible tamaño del código, NO hay necesidad de escribir proyectos grandes.



Ahora pasemos a C #. Hacemos una lista, y ... y se nos pide que indiquemos inmediatamente el TIPO de objeto. Creamos una lista de tipo Lista.



En tal lista puedes poner el número 1.



En tal lista puedes poner el número 2 e incluso, Dios me perdone, 3.




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


Pero las cadenas de texto ya no existen. Objetos de tu propia clase, estrictamente no. Guardo silencio sobre el Diablo Calvo y tu abuela, no pueden estar ahí bajo ninguna variante.



Puede hacer una lista separada de líneas. Puedes - para tus clases hechas por ti mismo.




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


De hecho, puede hacer listas, por separado, de Bald Devils, sus abuelas.



Pero agregarlos a una lista no funcionará. Su código ganará un premio Darwin, combinado con el abuso del compilador incluso antes de que intente ejecutarlo. El compilador prudentemente no le permite hacer la función DoSomeStuff (elemento) , que se "ahogará" con su argumento.



Esto es muy útil en proyectos grandes.

Pero, ¿qué hacer cuando todavía quiere ponerlo en una pequeña lista?



Esto no es realmente un problema. Basta con convertir todo al tipo de objeto . Casi (o incluso absolutamente) todo se puede convertir al tipo de objeto .




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


El problema comienza cuando comenzamos a recorrer la lista. El punto es que el tipo de objeto (casi) no puede hacer nada. Solo puede SER de tipo objeto .

- ¿Qué puedes hacer?

- Puedo cantar y bailar

- Y yo - Sancho ...

- ¿Qué puedes hacer tú, Sancho?

- Soy Sancho.

- Bueno, ¿puedes hacer algo?

- Usted no entiende. Puedo ser Sancho.



Por lo tanto, la interfaz está escrita. Esta es la clase de la que desea heredar. La interfaz contiene encabezados de método y propiedad.



En nuestro caso, estos son los métodos y propiedades que aseguran el funcionamiento NORMAL de la función DoSomeStuff (item) . La interfaz no implementa las propiedades en sí. Esto se hace a propósito. De hecho, uno podría simplemente heredar de alguna clase que pueda ser utilizada por la función DoSomeStuff () . Pero eso significa código extra y un programador olvidadizo.



Por lo tanto, si un compañero programador heredó una interfaz, pero olvidó implementar las propiedades y métodos requeridos de una clase, el compilador lo escribirá en el código con un Premio Darwin. Por lo tanto, puede hacer esto:




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);
}


Aquellos. para lo cual, al final, se necesita una interfaz, para hacer una lista escrita, o algún campo en la clase, y evitar la prohibición de agregar (a la lista o campo) algo de basura que quede allí.



La interfaz es "burocracia". No en todas partes está ni en todas partes se necesita, aunque sí, es necesaria y útil en grandes proyectos.



... en general algo así ... Pido disculpas por las duras expresiones, por alguna razón me parece que una presentación "seca" del material no tendría éxito ...



All Articles