La route vers la POO: le point de vue d'un ingénieur

Avertissement



L'article n'implique aucune vision fondamentalement nouvelle des choses, sauf du point de vue de l'étude de ce matériau à partir du «zéro absolu».





Le matériel est basé sur des notes d'il y a environ 7 ans, lorsque mon parcours dans l'étude de la POO sans formation en informatique ne faisait que commencer. À cette époque, MATLAB était le langage principal, beaucoup plus tard, je suis passé à C #.



La présentation des principes de la POO, que j'ai trouvé, avec des exemples sous forme de pommes, de poires héritées de la classe «fruit» et un tas de terminologie (héritage, polymorphisme, encapsulation, etc.), a été perçue comme une lettre chinoise.



Au contraire, maintenant, pour une raison quelconque, je perçois un tel matériel normalement, et la présentation de mon propre article semble parfois déroutante et longue.



Mais mes anciennes notes et l'horrible code qui subsiste sur les holodisques du pipboy montrent que la présentation "classique" ne remplissait pas ses fonctions à ce moment-là et a été complètement infructueuse. Il y a peut-être quelque chose là-dedans.



Dans quelle mesure cela correspond à la réalité et à vos propres préférences - décidez par vous-même ...



Prérequis pour la POO



Code du mur



Quand j'ai commencé à écrire dans MATLAB'e, c'était la seule façon d'écrire et je savais comment. Je connaissais les fonctions et que le programme pouvait être divisé en parties.



Le hic était que tous les exemples étaient nulles. J'ai ouvert le livre de cours de quelqu'un, j'ai vu de petites fonctions corporelles de 2-3 lignes, au total, tout cela n'a PAS fonctionné (il manquait quelque chose), et cela n'a fonctionné que lorsque j'ai réassemblé ces déchets dans un «mur».



Ensuite, j'ai écrit plusieurs fois de petits programmes, et à chaque fois je me suis demandé pourquoi il y avait quelque chose à partager. Ce n'est que plus tard que la compréhension est venue: le code "mur" - c'est l'état normal d'un programme d'environ 1,5 pages A4. Aucune fonction et, Dieu nous en préserve, la POO n'est PAS nécessaire là-bas.



Voici à quoi ressemble le script Matlab (tiré d'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)|')


Diviser le code en fonctions



Pourquoi le code est encore divisé en morceaux, j'ai deviné quand son volume a commencé à devenir complètement inimaginable (maintenant j'ai trouvé du code de merde dans les archives - 650 lignes par un mur). Et puis je me suis souvenu des fonctions. Je savais qu'ils vous permettaient de diviser votre code en petits blocs plus faciles à déboguer et à réutiliser.



Mais l'astuce est différente - pour une raison quelconque, tout le matériel pédagogique est muet sur COMBIEN une fonction de variables a ...



Le cours de mathématiques a dit qu'une fonction est y = f (x)



C'est ce qu'on appelle une «fonction d'une variable». Par exemple, y = x 2 est un PARABOL entier!

Problème mathématique: construire un PARABOL par points. Dans une feuille de cahier, dans une boîte.

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









, « », . , , . – . .



-…



image


Et si la fonction a quatre variables ou plus…. Théorie des supercordes. Variété Calabi-Yau. Mortel. Pas donné. Comprenez ...



Bref, tout est faux. En programmation, l'état normal d'une fonction est le double vaginal double anal . Il prend 100 variables et renvoie la même chose, ce qui est bien. Une autre chose est anormale - les lister avec un COMMA.



image


Sur le fait que vous pouvez écrire différemment, je me suis rendu compte quand je naval ICI




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 tas de variables séparées par un COMMA. Et le code appelant a des noms complètement différents pour ces paramètres, quelque chose comme SelectFun (a, b, c, d….) Par conséquent, vous devez vous rappeler où se trouve la variable. Et faites leur arrangement via le COMMA. Et si le code est en cours de modernisation et que le nombre de variables change, elles doivent être réorganisées à nouveau avec un COMMA.



Et pourquoi des variables globales (shoot!) Étaient-elles dans cette misère?



Bingo! Afin de ne pas organiser les variables avec chaque mise à niveau de code via un COMMA.



Mais le COMMA me suivait toujours, comme dans un cauchemar.



image


Et varargin est apparu. Cela signifie que je peux ajouter beaucoup plus d'arguments dans le code d'appel avec un COMMA ...



Et puis j'ai pensé aux tableaux. Les exemples de didacticiels parlaient avec enthousiasme du fait qu'un tableau peut être comme ceci:




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


Et vous voyez, X (2,3) = 6, et X (3,3) = 9, et nous ... nous pouvons organiser la multiplication matricielle sur de tels tableaux! Dans la dernière leçon, nous sommes passés par PARABOLS, et maintenant MATRIXES….



Et pas une seule ligne de ces putains de manuels n'est courte et claire: des tableaux sont nécessaires pour faire une fonction de 100 variables et ne pas tomber de leur liste via un COMMA.



image


En général, j'ai eu l'idée de tout entasser dans une grande table à deux dimensions. Tout s'est bien passé au début:




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)


Mais je voulais plus. Et cela a commencé à ressembler à ceci:




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)


Et tout semble aller bien, mais ... ZERO! Ils sont apparus parce que je voulais disperser des données hétérogènes sur différentes lignes, et que la quantité de données de différents types était différente ... Et comment la fonction devrait-elle traiter ces zéros? Que se passe-t-il si je souhaite mettre à jour le code? Je vais devoir réécrire le gestionnaire pour ces vilains zéros à l'intérieur de la fonction! Après tout, certaines des variables peuvent en fait être égales à zéro ...



Je n'ai jamais demandé cela ...



En général, c'est ainsi que j'ai appris les STRUCTURES.



Structures



C'est là qu'il était nécessaire de commencer la présentation sur les méthodes de conditionnement des données. Les tableaux avec une «table», apparemment, ont surgi en premier, et ils écrivent aussi à leur sujet - au début. En pratique, vous pouvez trouver de nombreux programmes où les tableaux sont unidimensionnels ou pas du tout.

La structure est un "dossier-dossier" contenant des données, approximativement sur le disque dur d'un ordinateur.

Lecteur D: \

X (dossier de variables - "objet" ou "structure")

- a.txt (fichier de variables avec données - "champ d'objet", champ anglais. Le numéro 5

est stocké) - b.txt (le numéro 10 est stocké )

- .txt

Y (variable-sous-dossier - "objet")

- d.txt (le numéro 2 est stocké)

- e.txt



Pour rendre les choses plus claires, écrivons comment nous verrions le chemin d'accès au fichier d.txt dans l'Explorateur Windows

D: \ X \ Y \ d.txt


Après cela, nous ouvrons le fichier et y écrivons le nombre "2".

Maintenant - à quoi cela ressemblera dans le code du programme. Il n'est pas nécessaire de faire référence au "lecteur local racine", donc D: \ n'est tout simplement pas là, et nous n'aurons pas non plus d'extension de fichier. Pour le reste, un point est généralement utilisé à la place d'une barre oblique \ dans la programmation.

Il s'avère que ceci:




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


Dans matlab, les structures ( struct ) peuvent être créées directement sur place, sans quitter la caisse, c'est-à-dire le code ci-dessus est exécutable, vous pouvez le conduire dans la console et tout fonctionnera immédiatement. La structure apparaîtra immédiatement, et tous les "fichiers-variables" et "sous-dossiers-variables" y seront ajoutés en même temps. Malheureusement, il est impossible de le dire à propos de C #, la structure ( struct ) y est fixée par les hémorroïdes.



La structure est un parent plus frais de la ARRAY OF TABLES, où au lieu d'index - un système de dossiers de fichiers. Structure = "variable-folder", qui contient des "variables-files" et d'autres "variables-folders" (c'est-à-dire une sorte de sous-dossiers).



Tout est familier, tout est exactement le même que sur un ordinateur, des dossiers, des fichiers, seuls les fichiers ne contiennent pas d'images, mais des chiffres (bien que des images soient également possibles).



Il s'agit d'une version plus avancée du stockage de données pour passer à une FUNCTION par rapport à l'idée de faire un ARRAY TABLE, en particulier en deux dimensions, et, dérange-moi, un tesseract, en trois dimensions et plus.

L'ARRAY TABLE est utilisable dans deux cas:

- il est petit (pourquoi alors? Quoi, vous ne pouvez pas passer d'arguments séparés par des virgules à la fonction?).

- soit vous pouvez faire une boucle dessus et automatiser la recherche / remplissage (ce n'est pas toujours possible)

En réalité, ARRAY TABLE est généralement utilisé uniquement comme une ligne unidimensionnelle de données homogènes. Tout le reste dans les programmes normaux est fait selon le schéma "dossier-fichier".



Alors pourquoi les manuels de programmation commencent-ils par des tableaux et des tableaux? !!!



Bref, "ayant découvert" les structures par moi-même, j'ai décidé que j'avais trouvé une mine d'or et tout réécrit de toute urgence. Le code merdique ressemblait à ceci:




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)


Oui, vous pouvez faire du perfectionnisme ici et créer un tas d'objets imbriqués, mais ce n'est pas le but. L'essentiel est que maintenant, à l'intérieur de la fonction, la variable est indexée non pas par son numéro ordinal (où elle se trouve dans la liste d'arguments, séparée par un COMMA), mais par son nom. Et il n'y a pas de zéros stupides. Et l'appel de fonction est maintenant d'une forme acceptable, il n'y a que 2 COMMANDES, vous pouvez expirer calmement.



Des classes



Le concept de «classe» m'a fait tomber une tonne de terminologie: encapsulation, héritage, polymorphisme, méthodes statiques, champs, propriétés, méthodes ordinaires, constructeur ... # @% !!! ..

Par inexpérience, ayant compris les structures, j'ai décidé qu'il n'y avait pas besoin de compliquer les entités inutilement, et je pensais - "les classes sont comme les mêmes structures, mais en plus compliquées."



Dans une certaine mesure, il en est ainsi. Plus précisément, c'est exactement ce que c'est. Une classe, si vous regardez très profondément, est une STRUCTURE (un descendant idéologique d'un tableau par une table), qui est créée lorsque le programme est DÉMARRÉ (en général, cela semble être, et pas seulement au démarrage). Comme pour tout descendant de ARRAY TABLE, les données y sont stockées. Ils sont accessibles pendant l'exécution du programme.



Par conséquent, ma première classe était quelque chose comme ça (j'écris un exemple en C #, dans matlab, les champs statiques ne sont normalement pas implémentés, uniquement via une courbe de hack avec des variables persistantes dans une fonction statique).



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

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


Le cas ci-dessus est, pour ainsi dire, la compétence "de base" d'une classe - être stupidement un tableau (structure) avec des données. Ces données y sont jetées au début du programme, et à partir de là, elles peuvent être extraites, exactement de la même manière que nous les avons extraites de la structure ci-dessus. Le mot-clé static est utilisé pour cela .



La structure -> est créée n'importe où et stocke les données qui y sont saisies à tout moment.



La classe -> est la structure créée au démarrage du programme. Tous les champs marqués du mot statique stockent simplement des données, comme dans une structure normale. Les méthodes statiques sont simplement des fonctions appelées depuis une classe, tout comme depuis un dossier.




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


J'ai eu un gag - si les champs sont des variables et les méthodes sont des fonctions, comment sont-elles stockées au même endroit? Si je comprends bien, une fonction (méthode) dans une classe n'est en fait pas une fonction, mais un pointeur vers une fonction. Ceux. c'est à peu près la même «variable» que pi en termes de travail avec elle.

En bref, au début, j'ai compris exactement les classes dans un tel volume et j'ai écrit un autre morceau de code de merde, où SEULEMENT des fonctions statiques étaient utilisées. Sinon, en tant que dossier avec des fonctions, je n'ai pas du tout utilisé de classes.



Ce point a également été facilité par le fait que c'est exactement ainsi que les classes sont faites dans MATLAB - comme un dossier aussi stupide, dont le nom commence par @ (comme @ Math, sans espace), à ​​l'intérieur, les fichiers réels avec l'extension .m sont des fonctions (méthodes) et il y a en-tête avec l'extension .m, ce qui explique que la fonction CircleLength appartient vraiment à la classe, et n'est pas simplement un fichier .m avec une fonction non-POO ajoutée.

@ Dossier Math%

- Fichier d'en-tête

Math.m% - Fichier de fonction CircleLength.m%

Oui, il existe une manière plus familière pour une personne normale d'écrire une classe dans un fichier .m, mais au début, je n'en savais rien. Les champs statiques dans matlab ne sont que constants et sont écrits une fois au démarrage du programme. Probablement afin de se protéger contre le "chalut", qui décide d'assigner Math.pi = 4 (à mon humble avis, sujet absolument inutile et stupide, aucune personne normale n'écrira un grand projet dans matlab, et un programmeur déboguera un petit projet et donc, il est peu probable il est un idiot).



Mais revenons au sujet. En plus des méthodes statiques, la classe possède également un constructeur. Un constructeur est fondamentalement juste une fonction comme y = f (x) ou même y = f (). Il peut ne pas avoir d'arguments d'entrée, il doit avoir une sortie, et c'est toujours une nouvelle structure (tableau).



Ce que fait le constructeur. Il fait juste des structures. Logiquement, cela ressemble à ceci:



Code C # Équivalent booléen approximatif (pseudocode)


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






Shit code sur matlab, créant des structures similaires sans aucune classe (où la classe est présente - voir ci-dessous):




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


Et à la sortie, nous avons la structure

Y (variable de dossier)

- a (variable de fichier, vaut 5)

- b (variable de fichier vaut 10)

À partir de là, en fait, il est clair que les champs dits de classe (non statiques, sans le code de clé statique ) sont des variables locales déclarées à l'intérieur de la fonction constructeur. Le fait qu'ils soient écrits pour une sorte de gobelin non pas dans le constructeur, mais à l'extérieur, est SYNTAX SUGAR.



SYNTAX SUGAR - de telles caractéristiques de conneries d'un langage de programmation, lorsque le code commence à donner l'impression de vouloir l'obscurcir au moment où il est écrit. Mais d'un autre côté, il devient plus court et plus rapide (soi-disant) écrit.



Ayant fait cette "découverte", moi qui à l'époque n'écrivais qu'en Matlab, j'ai été incroyablement surpris.



Dans matlab, comme je l'ai écrit plus haut, ces structures peuvent être créées en place, sans aucun constructeur, simplement en écrivant Ya = 5 , Yb = 10, tout comme vous dans le système d'exploitation, vous pouvez créer des fichiers et des dossiers sans quitter la caisse enregistreuse.



Et ici - une sorte de "constructeur", et tous les champs de la structure (dans le matlab, ils sont appelés propriétés - propriétés, bien que, à proprement parler, les propriétés soient une chose plus obscure que les champs) doivent être écrites bureaucratiquement dans le fichier d'en-tête. Pourquoi? Le seul avantage que j'ai vu alors dans ce système est que les champs de structure sont prédéfinis, et c'est comme "l'auto-documentation" - vous pouvez toujours voir ce qui devrait être là et ce qui ne devrait pas y être. Voici comment je m'entends alors:




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


Ceux. vous avez tout bien compris: les méthodes ne sont que statiques, le constructeur xs est pour quoi (c'est écrit dans la documentation - Oh, les classes doivent avoir un constructeur - eh bien, voici un constructeur pour vous), tout le reste je ne savais pas bêtement et j'ai décidé que je connaissais Zen et POO.



Mais néanmoins, cela m'a semblé une bonne idée de collecter des fonctions (méthodes statiques) par class-dossiers. il y en avait beaucoup, et je me suis assis pour écrire du code de merde.



Bureaucratie



Et est tombé sur une telle chose. Il existe un ensemble de fonctions d'un niveau de logique inférieur (elles sont statiques et sont emballées dans des dossiers-classes, nous allons maintenant omettre les noms des classes):




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


Dans les petits projets, il est impossible d'atteindre une telle domination des fonctions, les exemples pédagogiques contiennent généralement 2-3 fonctions - comme "voir comment on peut construire un PARABOL".



Et ici - un putain de nuage de fonctions, et chacune d'elles, sa mère, a chacune un argument de sortie, et que faire de toutes? Mettez dans des fonctions d'un niveau de logique plus élevé ("leader")! Habituellement, il y en a beaucoup moins (conventionnellement, 5 au lieu de 20). Ceux. conditionnellement, vous devez en quelque sorte prendre ces Y1, Y2, Y3… .Y20 et les RIPPED dans certains Z1, Z2… Z5. Pour que plus tard vous puissiez faire une réunion de la fête et y participer:




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


Mais Z1… Z5 ne viennent pas d’eux-mêmes. Pour les créer, vous avez besoin de FUNCTIONS-PACKERS. Classiquement, ils fonctionnent quelque chose comme ça ...




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


Et puis il y a peut-être un autre niveau "management" ...



Bref, je me suis rendu compte que j'étais dans un enfer logistique. Je ne pourrais pas normalement extraire des données d'un CLOUD FIGURE de petites fonctions y = f (x) sans écrire un FIGURE CLOUD de fonctions de reconditionnement-bureaucratiques, et lorsque les données sont transférées à un niveau supérieur, nous avons besoin de plus de REPLACERS. Le programme final regorge de bureaucratie de bout en bout - il y a plus de reconditionneurs que de «code d'entreprise». Les classes de dossiers pour fonctions ne résolvent pas ce problème - elles rassemblent simplement les repackers idiots bureaucratiques en tas.



Et puis j'ai décidé de moderniser ce code merdique, et il s'est avéré que sans scier toute la partie bureaucratique, c'est impossible!



Tout comme la vie en Russie ...



J'ai réalisé que je faisais quelque chose de mal et j'ai mieux compris la POO. Et la solution - si vous regardez les choses de cette façon, c'était idéologiquement à la surface.



Idée POO



Pourquoi faire un tas de fonctions comme y = f (x) qui produisent DIFFÉRENTS arguments de sortie Y1… .Y20 , alors que vous pouvez faire UN argument. Sorte de:




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


Alors absolument tous les résultats de la fonction seront poussés dans une structure, dans un seul tableau, juste dans ses différents compartiments. Tout. Ensuite, Y_all peut être transféré directement vers le haut, vers le niveau supérieur de la "gestion".




Y_all=DO_MOST_IMPORTANT_SHIT(Y_all, options_how_to_do_this_shit)


Toutes les fonctions-SEALERS-BUREAUERS vont au cul! Toutes les données sont collectées dans la base ONE Y_all , toutes les fonctions de bas niveau placent le fruit de leur travail dans différents compartiments Y_all , la «gestion» parcourt tous les compartiments Y_all et fait ce qu'elle doit faire. Rien de superflu, le code s'écrit rapidement et fonctionne très bien ...



C'est exactement l'idée de la POO et se compose. Dans les manuels, ils écrivent des exemples pédagogiques sur les pommes et les poires, puis présentent un programme en 5 lignes. Il n'y a pas du tout besoin de POO, dans les exemples pour 5 lignes, car le transfert des données au "niveau de la direction" se fait directement sans problème.



La POO est nécessaire lorsqu'un grand projet est le problème de la "bureaucratisation" ...

Mais revenons au point. Dans la vraie POO, il y a SYNTAX SUGAR. L'exemple ci-dessus avec Y_all utilisé uniquement des structures, les fonctions f (,,,) seront considérées comme statiques. La POO est un ensemble de sucre lorsque le code commence à ressembler à ceci:




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


Ceux. nous avons en quelque sorte décidé d'apporter une syntaxe floue dans laquelle vous ne pouvez pas écrire Y_all 2 fois, mais ne le faites qu'une fois. Car la répétition est la mère du bégaiement.



Le reste de l'explication «comment fonctionne la POO» se résume à expliquer comment fonctionne le sucre syntaxique.



Comment fonctionne le sucre syntaxique OOP



Tout d'abord, cette base de données Y_all , évidemment, doit être créée avant d'être utilisée comme argument de la fonction. Cela nécessite un constructeur.



Deuxièmement, il convient de prévoir, de préférence à l'avance, quels "compartiments" il comportera. Tant que la base de données Y_all est petite, ce paramètre est ennuyeux. J'aimerais rêver de "classes créées à la volée", à peu près de la même manière que dans MATLAB vous pouvez créer des structures avec des commandes simples Ya = 5 , Yb = 10 . Mais l'envie de fantasmer sur ce sujet disparaît après le débogage d'un projet sain.



Suivant - appel de la méthode (fonction).



C'est ainsi qu'il a approximativement évolué

Une fonction Commentaire
Y = f (X) C'était le cas en mathématiques lorsque nous avons tracé un PARABOL par points!
X = f (X) Nous avons été intimidés par des bureaucrates, et nous avons un lot d' un argument pour toutes les occasions, stockant toutes les données d'entrée et de sortie dans différents compartiments à l'intérieur
f (X) Pourquoi une fonction devrait-elle retourner un argument? C'est l'archaïsme du temps des cours de maths! Et une perte de mémoire inutile! Laissez les données être passées par référence, puis la fonction elle-même viendra à l'argument, changera et partira. RIEN = f (X)

Ce n'est pas la montagne qui va à Mohammed, mais Mohammed à la montagne.
X.f () Nous venons de sortir l'argument X avec le sucre syntaxique. RIEN = X.f (RIEN)




Maintenant - comment une telle fonction est-elle organisée en interne qui ne prend RIEN et ne renvoie RIEN (le mot-clé void en C #).



J'aime la façon dont cela se fait dans matlab (du point de vue de la compréhension): la fonction que nous appelons Xf () est écrite en interne comme

Exemple de code MATLAB Exemple de code 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 ( ).





Voici une fonction avec "l'argument par défaut est ceci ", se trouvant dans la classe, comme dans un dossier - il y a une méthode ordinaire (xs, comme elle est correctement en russe).

En fait, entasser tous les arguments en un seul ce n'est pas toujours correcte. Parfois, vous avez besoin d'autres arguments (par exemple, il s'agit d'une entrée utilisateur):




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


Parfois, vous devez même renvoyer un argument (par exemple, sur le succès ou l'échec d'une opération), et ne pas écrire void . Ce qui, cependant, ne change pas les statistiques: la plupart des fonctions POO ne renvoient RIEN ( void ) et n'acceptent rien (l'argument par défaut ne compte pas) ou très peu d'arguments.



Écrivons le code final

dans 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


Maintenant 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


Quand j'ai compris, il m'a semblé que la plupart des problèmes d'écriture de code avaient disparu en arrière-plan ...



Propriétés vs champs



Regardons un exemple.

sans propriétés avec des propriétés

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;


commentaire: l'argument

Set_a peut être appelé quel que soit

Set_a (int YourVarName)

commentaire: une variable à l'intérieur de

set {...} doit toujours être appelée valeur



Cette chose est assez pratique et souvent utilisée, mais c'est toujours SYNTAX SUGAR.

Le champ est une variable pleinement qualifiée. La propriété est 2 méthodes de classe (get et set), dont la syntaxe d'appel copie "appel variable".



En fait, à l'intérieur de get and set, vous pouvez faire de la merde:




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


Par conséquent, il semble qu'il soit recommandé d'écrire les propriétés du nom avec une majuscule et les champs avec une minuscule.



Il arrive (par exemple, vous ne pouvez pas créer un champ dans les interfaces) que vous deviez faire une propriété rapidement, alors vous pouvez:




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


Héritage, encapsulation, polymorphisme



Pourquoi ne les avez-vous pas mentionnés auparavant? Parce que

- en fait, lors de l'écriture de code, ils ne sont pas demandés avec une telle force qu'ils sont mentionnés dans la requête "Ok Google, qu'est-ce que la POO". Je dirais même qu'au début, ils sont pratiquement inutiles .

- là où ils sont nécessaires, vous pouvez lire à leur sujet (seuls les paresseux n'ont pas écrit sur ce cas).
Quand il y a un processus de maîtrise des compétences d' écriture de style POO

- vous aurez la plupart des classes SANS héritage. Vous écrivez simplement TOUTES les fonctionnalités dans la classe requise et vous n'avez pas vraiment besoin d'hériter de quelque chose.

- en conséquence, le polymorphisme (une lotion à l'héritage) passe aussi par la forêt

- "l'encapsulation" se réduira à l'assignation publique partout (à tous les champs, propriétés et méthodes).

Ensuite, vos mains se développeront jusqu'à vos épaules et vous découvrirez vous-même, sans cet article , où vous ne devriez PAS faire cela, surtout où vous ne devriez PAS écrire en public.



Mais encore un bref aperçu d'eux.



Héritage. C'est un copier-coller intelligent



Une implémentation défectueuse de "l'héritage" ressemble à ceci:

Oh, mon code de merde a une classe appelée MyClass, et il manque encore un champ SHIT et une autre méthode DO_THE_SHIT ()!

* Ctrl + C, Ctrl + V

* Une nouvelle classe MyClass_s_fichami est créée et celle souhaitée y est ajoutée

Pourtant, nous sommes des gens plus civilisés et nous savons qu'il vaut mieux ne pas copier le texte du programme, mais s'y référer.



Disons que nous écrivons toujours dans un ancien langage de programmation ou que nous ne sommes pas conscients d'une chose telle que "l'héritage". Ensuite, nous écrivons 2 classes différentes


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




Ce que nous avons fait à droite, c'est l'héritage. Uniquement dans les langages de programmation normaux, cela se fait avec une seule commande:




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


Le code fonctionne "à l'extérieur" exactement de la même manière que dans l'option 2. Ie. l'objet, pour ainsi dire, devient une "matriochka" - à l'intérieur d'un objet, un autre objet est assis bêtement, et il y a des "canaux de communication" sur lesquels vous pouvez vous référer directement à l'objet interne.



En-tête de spoiler
image



Dans matlab, la situation est un peu plus intéressante. Lorsque vous exécutez le constructeur enfant, MyClassB , il n'y a aucun appel silencieux au constructeur ancêtre MyClassA .



Vous devez le créer directement. D'une part, c'est ennuyeux:




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


Mais si le descendant est appelé avec d'autres arguments, tels que MyClassB (d) , alors vous pouvez faire une conversion à l'intérieur, quelque chose comme:




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


En C #, cela ne peut pas être fait directement, ce qui oblige à écrire une sorte de «fonctions de conversion»:




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


ou faites des "constructeurs statiques" comme ceci:




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


Cela semble être une question d'héritage, essentiellement tout.



Naturellement, personne n'oblige l'héritier à écrire la méthode " F1 " et la propriété " a " pour qu'elles soient nécessairement traduites en appel de la méthode et des champs de l'ancêtre. La diffusion n'est que le comportement "d'héritage" par défaut.



Vous pouvez (bien sûr! Ce sont d'autres méthodes dans une autre classe, bro) écrire comme ceci:




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


Encapsulation



... Conceptuellement, cela signifie qu'à l'intérieur d'un objet de la classe MyClassB, un objet de la classe MyClassA se trouve dans le champ de base, avec la possibilité de diffuser des commandes de contrôle à l'extérieur. Tout cela est écrit ci-dessus et n'a pas de sens à répéter.



Il y a un tel sujet avec différents modificateurs d'accès - public , privé , protégé ... A propos d'eux, ce qui est le plus intéressant, il est écrit partout plus ou moins normalement, je recommande juste de le lire.

public - cela signifie que le champ , la propriété ou la méthode sera visible de l'extérieur et peut être extrait.

Si vous ne savez pas quoi faire, écrivez en public (mauvais conseil, oui).



Puis trouvez la force en vous et jetez ce public (ou, pour plus de clarté, remplacez-le par privé ) partout où il n'est pas nécessaire (faites un "refactoring"). Oui, bien sûr, c'est très bien d'être un visionnaire, d'agir dans la bataille des médiums et de deviner tout de suite où rendre privé .

private - cela signifie que le champ , la propriété ou la méthode d'un objet "dossier-fichier" est visible uniquement à partir des méthodes de cette classe.

MAIS ... C'est une classe, pas une INSTANCE (objet). Si vous avez un code comme:




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


Une telle chose est utilisée dans le clonage (voir d'autres sources pour plus de détails).



J'ai essayé de penser à cette disposition du public et du privé lors de l'écriture de code . Il s'agit d'un code qui prend trop de temps lors de la rédaction du code. Et puis il s'avère que le code lui-même doit être fait d'une manière fondamentalement différente.



Si le code est écrit en solo, alors cela n'a aucun sens de se soucier du privé et du public à l' avance, il y a des tâches plus importantes, par exemple, trouver et écrire le code ...

Le seul endroit où il est plus ou moins clair à quel endroit placer privé et public sont les mêmes propriétés notoires qui se réfèrent à une sorte de champ.




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


Dans d'autres endroits, pour organiser le public et le privé, vous devez vraiment regarder ce que fait le programme, et il est fort probable que cela ne fonctionnera pas d'apprendre cela "par contumace".

protected - cela signifie « public » pour toutes les méthodes des classes dérivées et « privé » pour tout le reste.

En général, il est logique de supposer que les classes héritées apparaissent simplement comme des «versions plus sophistiquées» de leurs ancêtres.



Honnêtement, j'ai déjà oublié où j'ai explicitement appliqué cette protection. Généralement public ou privé. La plupart des classes que j'ai écrites n'héritaient d'aucune autre classe personnalisée, et là où elles le faisaient, il y avait rarement un besoin sérieux de telles choses.



L'impression est que les modificateurs non publics sont demandés lorsque l'on travaille sur un grand projet, qui peut être soutenu par un groupe de personnes ... La compréhension de l'endroit où les appliquer n'apparaît qu'après une longue période de collage dans un code d'un kilomètre. Lorsqu'on étudie «par correspondance», il est difficile de donner en quelque sorte cette compréhension.



Polymorphisme



Quand j'écrivais dans Matlab, je ne comprenais pas du tout pourquoi le polymorphisme était nécessaire et CE QUE C'EST.

Puis, quand je suis passé en C #, j'ai réalisé que c'était une caractéristique des LANGUES STRICTEMENT TYPIQUES, et qu'il avait une relation très faible avec la POO. Dans matlab, vous pouvez écrire partout sans connaître l'existence de ce polymorphisme - il n'y a pas de typage strict.



Pour plus de simplicité, appelons les classes A et B




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


C'est ce qu'on appelle le typage. En C #, vous pouvez VOUS-MÊME (si vous savez comment) écrire vos propres systèmes de casting de types personnalisés, de presque n'importe quel type à n'importe quel autre.

Ici - juste "lancer" hors de la boîte. Puisqu'un autre objet de classe A se trouve à l' intérieur d'un objet x appartenant à la classe B , alors l'une des façons apparemment évidentes de transtyper est de fermer toutes les connexions d'un objet externe à un objet interne. Ce n'est pas vraiment nécessaire, mais ceux qui ont inventé le «polymorphisme» ont décidé qu'il serait très évident de le faire. Et l'utilisateur écrira lui-même le reste des options. Désolé pour le (plus tout à fait pertinent) "politota" de l'échantillon 2008-2012.










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


Interface



Nous devons commencer par appliquer CECI.



Disons que nous avons une liste et que nous voulons y mettre quelque chose.



Dans matlab, le moyen le plus simple de le faire est (appelé un tableau de cellules):




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


Vous ne pensez pas de quel type d'objet il s'agit, il suffit de le prendre et de le mettre sur une liste.



Ensuite, disons que vous devez parcourir la liste et faire quelque chose avec chaque élément:




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


Si la fonction DoSomeStuff est assez intelligente pour digérer tout ce qui lui est fourni, ce code SERA REMPLI.



Si la fonction DoSomeStuff (ou son auteur) ne brille pas d'intelligence, alors il y a une possibilité de s'étouffer avec quelque chose: un nombre, une chaîne, votre classe créée par vous-même, le diable chauve ou - Dieu nous en préserve - votre grand-mère.



MATLAB affichera les jurons rouges en anglais dans la console et mettra fin à votre programme. Ainsi, votre code recevra automatiquement un Darwin Award.



Cependant, c'est en fait mauvais car parfois le code est très complexe. Ensuite, vous serez fermement convaincu que vous avez tout fait correctement, mais en fait, la combinaison erronée d'actions n'a tout simplement jamais été lancée pendant les tests.



C'est pourquoi (mais pas seulement parce que) sur MATLAB - j'ai réussi à m'en assurer moi-même (à peu près comme sur KPDV), sur la taille terrible du code - il n'y a PAS BESOIN d'écrire de grands projets.



Passons maintenant à C #. On fait une liste, et ... et on nous demande d'indiquer immédiatement le TYPE de l'objet. Nous créons une liste de type List.



Dans une telle liste, vous pouvez mettre le numéro 1.



Dans une telle liste, vous pouvez mettre le numéro 2 et même, Dieu me pardonne, 3.




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


Mais les chaînes de texte ne sont plus là. Des objets de votre propre classe - strictement pas. Je suis silencieux sur le diable chauve et votre grand-mère, ils ne peuvent être là sous aucune variante.



Vous pouvez créer une liste séparée de lignes. Vous pouvez - pour vos cours personnalisés.




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


En fait, vous pouvez faire des listes - séparément - de Bald Devils, vos grands-mères.



Mais les ajouter dans une seule liste ne fonctionnera pas. Votre code gagnera un prix Darwin, combiné avec un abus de compilateur avant même d'essayer de l'exécuter. Le compilateur ne vous autorise pas prudemment à créer la fonction DoSomeStuff (item) , qui "s'étouffera" avec son argument.



C'est vraiment pratique dans les grands projets.

Mais que faire quand vous voulez toujours le mettre dans une petite liste?



Ce n'est pas vraiment un problème. Il suffit de tout convertir en objet de type . Presque (ou même absolument) tout peut être converti en objet de type .




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


Le problème commence lorsque nous commençons à parcourir la liste. Le fait est que le type d' objet ne peut (presque) rien faire. Il ne peut être que de type objet .

- Que pouvez-vous faire?

- Je peux chanter et danser

- Et moi - Sancho ...

- Que peux-tu faire, Sancho?

- Je suis Sancho.

- Tu peux faire quelque chose?

- Tu ne comprends pas. Je peux être Sancho.



Par conséquent, l'interface est écrite. Il s'agit de la classe dont hériter. L'interface contient des en-têtes de méthode et de propriété.



Dans notre cas, ce sont les méthodes et propriétés qui assurent le fonctionnement NORMAL de la fonction DoSomeStuff (item) . L'interface n'implémente pas les propriétés elles-mêmes. C'est fait exprès. En fait, on pourrait simplement hériter d'une classe qui peut être utilisée par la fonction DoSomeStuff () . Mais cela signifie du code supplémentaire et un programmeur oublieux.



Par conséquent, si un autre programmeur hérite d'une interface, mais oublie d'implémenter les propriétés et méthodes requises d'une classe, le compilateur l'écrira dans le code avec un prix Darwin. Ainsi, vous pouvez faire ceci:




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


Ceux. pour lequel, à la fin, une interface est nécessaire - pour faire une liste tapée, ou un champ dans la classe, et contourner l'interdiction d'ajouter (à la liste ou au champ) des déchets laissés là-bas.



L'interface est la «bureaucratie». Il n'est pas nécessaire partout et pas partout, même si oui, il est nécessaire et utile dans les grands projets.



... en général, quelque chose comme ça ... Je m'excuse pour les expressions dures, pour une raison quelconque, il me semble qu'une présentation "sèche" du matériel serait un échec ...



All Articles