LXF143:c

Материал из Linuxformat.

Перейти к: навигация, поиск
Язык C# Как обой­ти его ог­ра­ни­че­ния при пе­ре­но­се ко­да, на­пи­сан­но­го на C++

Содержание

C# и Mono: Сте­рео­эф­фект

Ан­д­рей Кузь­мен­ко про­дол­жа­ет рас­сказ о пе­ре­но­се ко­да с C++ на C#. В этой ста­тье речь пой­дет о мно­же­ст­вен­ном на­сле­до­ва­нии.

Сло­жи­лось так, что язык C# не под­дер­жи­ва­ет мно­же­ст­вен­ное на­сле­до­вание. Спо­ры о том, хо­ро­шо это или пло­хо, про­дол­жа­ют­ся уже дол­гое вре­мя, од­на­ко по боль­шей час­ти они принима­ют вид «ре­ли­ги­оз­ной вой­ны» и но­сят ско­рее тео­ре­ти­че­­ский ха­рак­тер. С дру­гой сто­ро­ны, при объ­ект­но-ори­ен­ти­ро­ван­ном под­хо­де к про­ек­ти­ро­ванию про­грамм­но­го обес­пе­чения мно­же­ст­вен­ное на­сле­до­вание час­то ока­зы­ва­ет­ся ме­ханиз­мом, наи­бо­лее аде­к­ват­но опи­сы­ваю­щим мо­де­ли­руе­мый объ­ект и его по­ве­дение. Кро­ме то­го, мо­жет возник­нуть необ­хо­ди­мость вы­полнить ре­ин­жиниринг су­ще­ст­вую­ще­го про­грамм­но­го обес­пе­чения: на­при­мер, пе­ренести код, на­пи­сан­ный на C++ с ис­поль­зо­ванием мно­же­ст­вен­но­го на­сле­до­вания, на плат­фор­му Mono. В дан­ной ста­тье бу­дет рас­смот­рен один из воз­мож­ных ва­ри­ан­тов под­хо­да к реа­ли­за­ции по­до­бия «на­стоя­ще­го» мно­же­ст­вен­но­го на­сле­до­вания в язы­ке C#. Хо­чу сра­зу пре­ду­пре­дить чи­та­те­ля о том, что все те воз­мож­но­сти в том ви­де, как они есть в язы­ке C++, в рам­ках плат­фор­мы Mono по­лу­чить нель­зя; од­на­ко мож­но про­вес­ти адап­та­цию имею­щих­ся язы­ко­вых средств и ис­поль­зо­вать ин­те­рес­ные и по­лез­ные прак­ти­че­­ские ре­зуль­та­ты для ре­шения оп­ре­де­лён­но­го клас­са за­дач – а имен­но, объ­е­динения раз­но­род­ных ие­рар­хий клас­сов.

Встать, суд идёт

Мно­же­ст­вен­ное на­сле­до­вание по­зво­ля­ет соз­дать про­из­вод­ный класс на осно­ве несколь­ких ба­зо­вых клас­сов. При этом кри­ти­ки тра­ди­ци­он­но ука­зы­ва­ют на две воз­мож­ные про­бле­мы. Пер­вая за­клю­ча­ет­ся в том, что по­яв­ля­ет­ся воз­мож­ность унас­ле­до­вать од­но и то­же же имя по­ля дан­ных или ме­то­да от несколь­ких ба­зо­вых клас­сов, что мо­жет по­слу­жить при­чи­ной не­од­но­знач­но­сти:

//Ро­ди­тель­ские клас­сы
 class First { int function(int x); };
 class Second { int function(int x); };
 // До­чер­ний класс
 class Result : First, Second { }
 // соз­дан объ­ект клас­са
 Result res = new Result();
 // ка­кой имен­но ме­тод бу­дет вы­зван?
 res.function(10);

Вто­рая про­бле­ма – это «ром­бо­вид­ное» на­сле­до­ва­ние, ко­гда в ие­рар­хии от ба­зо­во­го клас­са к про­из­вод­но­му су­ще­ст­ву­ет бо­лее од­но­го пу­ти. Вот клас­си­че­ский при­мер:

class File{ string file_name};
 class InputFile : File {};
 class OutputFile : File {};
 class IOFile : InputFile, OutputFile {};

Возника­ет во­прос: долж­ны ли по­ля дан­ных ба­зо­во­го клас­са дуб­ли­ро­вать­ся в объ­ек­те под­клас­са столь­ко раз, сколь­ко су­ще­ст­ву­ет пу­тей ме­ж­ду ними в ие­рар­хии на­сле­до­вания? В язы­ках, под­дер­жи­ваю­щих мно­же­ст­вен­ное на­сле­до­вание, та­ких как С++, Eiffel, Python, это ре­ша­ет­ся по-раз­но­му.

Спра­вед­ли­во­сти ра­ди за­ме­чу, что спи­сок труд­но­стей, воз­ни­каю­щих при мно­же­ст­вен­ном на­сле­до­вании, не ис­чер­пы­ва­ет­ся те­ми дву­мя, что опи­са­ны вы­ше; од­на­ко су­ще­ст­ву­ют си­туа­ции, когда мно­же­ст­вен­ное на­сле­до­вание дей­ст­ви­тель­но оп­рав­да­но – на­при­мер, объ­е­динение неза­ви­си­мых ие­рар­хий клас­сов, ком­по­зи­ция ин­тер­фей­сов, соз­дание клас­са из ин­тер­фей­са и реа­ли­за­ции. На­ли­чие ме­ханиз­ма мно­же­ст­вен­но­го на­сле­до­вания де­ла­ет язык про­гра­ми­ро­вания вы­ра­зи­тельнее и бо­га­че, и наи­бо­лее спра­вед­ли­вый путь – это дать вы­бор про­грам­ми­сту: ис­поль­зо­вать имею­щие­ся воз­мож­но­сти или нет.

Тео­рия

Ес­ли класс рас­смат­ри­ва­ет­ся как ме­ханизм для пред­став­ления неко­то­рых сущ­но­стей, то ин­тер­фей­сы мож­но понимать как опи­сание неко­то­рых дей­ст­вий над эти­ми сущ­но­стя­ми. По су­ти сво­ей, ин­тер­фей­сы в язы­ке C# очень по­хо­жи на вир­ту­аль­ные ме­то­ды аб­ст­ракт­но­го клас­са в язы­ке C++. Они опи­сы­ва­ют груп­пу свя­зан­ных функ­цио­наль­ных воз­мож­но­стей, ко­то­рые мо­гут при­над­ле­жать лю­бо­му клас­су и иметь ме­то­ды, свой­ст­ва, со­бы­тия, ин­дек­са­то­ры или лю­бое их со­че­тание. Ин­тер­фей­сы не мо­гут со­дер­жать по­ля дан­ных. Кро­ме то­го, они не со­дер­жат реа­ли­за­ции ме­то­дов. Когда го­во­рят, что класс на­сле­ду­ет ин­тер­фейс, это оз­на­ча­ет, что класс пре­достав­ля­ет реа­ли­за­цию для всех чле­нов, оп­ре­де­ляе­мых ин­тер­фей­сом. Та­ким об­ра­зом, в ин­тер­фей­се мы за­яв­ля­ем, что хо­тим сде­лать, но не оп­ре­де­ля­ем, как это бу­дет кон­крет­но реа­ли­зо­ва­но. Смысл в том, что для раз­ных клас­сов мы реа­ли­зо­вы­ва­ем од­но­тип­ный на­бор ме­то­дов с одним ви­дом их вы­зо­ва, но при этом реа­ли­за­ция ме­то­дов бу­дет раз­лич­аться в раз­ных клас­сах. До­пуска­ет­ся на­сле­до­вание про­из­воль­но­му чис­лу ин­тер­фей­сов.

Су­ще­ст­вую­щая в язы­ке C# за­ме­на «пол­но­цен­но­го» мно­же­ст­вен­но­го на­сле­до­вания клас­сов на на­сле­до­вание ин­тер­фей­сов вы­гля­дит сле­дую­щим об­ра­зом: есть ба­зо­вый класс, ко­то­рый име­ет неко­то­рый на­бор дан­ных и ме­то­дов. Есть один или несколь­ко ин­тер­фей­сов, ко­то­рые пред­по­ла­га­ют вы­полнение клас­сом неко­то­рых опе­ра­ций. Про­из­вод­ный класс на­сле­ду­ет «пол­но­цен­но­му» клас­су, по­лу­чая от него неко­то­рые дан­ные и ме­то­ды, плюс принима­ет на се­бя обя­за­тель­ст­ва по реа­ли­за­ции тех ин­тер­фей­сов, ко­то­рым он на­сле­ду­ет.

По­ня­тие ин­тер­фей­са лег­ко ил­лю­ст­ри­ру­ет­ся про­стым жи­тей­ским при­ме­ром. Пред­ставь­те се­бе мно­же­ст­во пред­ме­тов, от­но­ся­щих­ся к бы­то­вой элек­тронике: ви­део­магнито­фон, DVD-про­иг­ры­ва­тель, CD-плей­ер, ав­то­магнито­лу. Про­из­ве­дён­ные раз­ны­ми фир­ма­ми-из­го­то­ви­те­ля­ми и вы­пол­няю­щие раз­ные функ­ции, они все име­ют унифи­ци­ро­ван­ный поль­зо­ва­тель­ский ин­тер­фейс: ес­ли на­жать на кноп­ку с на­ри­со­ван­ным тре­угольником, уст­рой­ст­во бу­дет «иг­рать», а ес­ли на­жать кноп­ку с квад­ра­том, воспро­из­ве­дение пре­кра­тит­ся.

Прак­ти­ка

Да­вай­те рас­смот­рим про­грамм­ную мо­дель мо­биль­но­го те­ле­фо­на, ко­то­рый мо­жет ис­поль­зо­вать­ся как MP3‑плей­ер, для ра­бо­ты с ко­то­рым поль­зо­ва­те­лю доста­точ­но на­жи­мать со­от­ве­ст­вую­щие кноп­ки на кор­пу­се.

using System;
 namespace Gadget
 {
 	 class MobilePhone
 	 {
 		 public void make_call(string number)
 {
 Console.WriteLine(“Call:” + number);
 }
 	 }
 
 	 interface IManagement
 	 {
 		 void play( );
 		 void stop( );
 	 }
 
 	 class Gadget : MobilePhone, IManagement
 	 {
 		 public void usb_connect( )
 {
 Console.WriteLine(“Connect to computer…”);
 }
 		 public void play( ) { Console.
 WriteLine(“Music playing now!”);}
 		 public void stop( ) { Console.WriteLine(“Stop playing music.”); }
 
 	 }
 
 	 class Program
 	 {
 		 public static void Main(string[] args)
 		 {
 			 Gadget g = new Gadget();
 			 g.play();
 			 g.stop();
 			 g.make_call(89991234567);
 		 }
 	 }
 }

Страш­ная тай­на

В ли­те­ра­ту­ре, будь то учебники для на­чи­наю­щих или спра­вочники для про­фес­сио­на­лов, от­вет на во­прос о том, по­че­му же в язы­ке C# нет «нор­маль­но­го» мно­же­ст­вен­но­го на­сле­до­вания для клас­сов, за­час­тую очень рас­плыв­чат. Дес­кать, мно­же­ст­вен­ное на­сле­до­вание слож­но для понимания и яв­ля­ет­ся ис­точником по­тен­ци­аль­ных оши­бок. Я ду­маю, что сто­ит при­под­нять за­ве­су та­ин­ст­вен­но­сти и ра­зо­брать­ся в этой си­туа­ции.

Вы­ше вы уже про­чли, что воз­мож­но унас­ле­до­вать од­но и то­ же имя по­ля дан­ных или ме­то­да от несколь­ких ба­зо­вых клас­сов. Как же тут быть? Язык C++ пред­ла­га­ет такое ре­шение: ес­ли нет необ­хо­ди­мо­сти дуб­ли­ро­вать ин­фор­ма­цию в про­из­вод­ном клас­се, то все непо­сред­ст­вен­ные по­том­ки ба­зо­во­го клас­са в ром­бо­вид­ной схе­ме долж­ны на­сле­до­вать сво­ему пред­ку вир­ту­аль­но:

class File{};
 class InputFile : public virtual File {};
 class OutputFile : public virtual File {};
 class IOFile : public InputFile, OutputFile {};

Та­ким об­ра­зом, File ста­но­вит­ся вир­ту­аль­ным ба­зо­вым клас­сом. Этот ва­ри­ант, бу­ду­чи про­стым и вполне по­нят­ным для про­грам­ми­ста, тре­бу­ет оп­ре­де­лён­ных «тру­до­за­трат» со сто­ро­ны ком­пи­ля­то­ра. Про­бле­ма в том, что вир­ту­аль­ные ба­зо­вые клас­сы реа­ли­зу­ют­ся как ука­за­те­ли. При этом раз­мер ко­да уве­ли­чи­ва­ет­ся, доступ к по­лям дан­ных вир­т­уаль­ных ба­зо­вых клас­сов ока­зы­ва­ет­ся мед­леннее по сравнению с невир­ту­аль­ны­ми ба­зо­вы­ми клас­са­ми, а сам ком­пи­ля­тор, ко­то­рый всё это реа­ли­зу­ет, ста­но­вит­ся «тя­же­лее». Важ­ное за­ме­чание: к мо­мен­ту оп­ре­де­ления клас­сов InputFile и OutputFile нет ника­кой ин­фор­ма­ции о том, бу­дет ли когда-нибудь ка­кой-ли­бо класс на­сле­до­вать от них обо­их или нет. Ес­ли из­на­чаль­но не объ­я­вить класс File вир­ту­аль­ным ба­зо­вым клас­сом, то мо­жет сло­жить­ся так, что раз­ра­бот­чи­ку клас­са IOFile по­тре­бу­ет­ся пе­ре­оп­ре­де­лить клас­сы InputFile и OutputFile, а это бывает невоз­мож­ным по при­чине то­го, на­при­мер, что дан­ные клас­сы на­хо­дят­ся в ском­пи­ли­ро­ван­ной биб­лио­те­ке.

При этом пра­ви­ла, со­глас­но ко­то­рым про­ис­хо­дит инициа­ли­за­ция вир­ту­аль­ных ба­зо­вых клас­сов, ока­зы­ва­ют­ся сложнее, чем в слу­чае «про­сто­го» на­сле­до­вания. При невир­ту­аль­ном на­сле­до­вании ар­гу­мен­ты кон­ст­рук­то­ра ба­зо­во­го клас­са за­да­ют­ся в спи­ске инициа­ли­за­ции непо­сред­ст­вен­но­го про­из­вод­но­го клас­са – та­ким об­ра­зом, ар­гу­мен­ты пе­ре­да­ют­ся со­вер­шен­но оче­вид­ным спо­со­бом: клас­сы уров­ня N транс­ли­ру­ют ар­гу­мен­ты вверх, клас­сам уров­ня (N–1). А в слу­чае вир­ту­аль­но­го на­сле­до­вания от­вет­ст­вен­ность за инициа­ли­за­цию ба­зо­во­го клас­са ло­жит­ся на са­мый по­следний до­черний класс в ие­рар­хии. По этой при­чине клас­сы, на­сле­дую­щие вир­ту­аль­но­му ба­зо­во­му и ну­ж­даю­щие­ся в инициа­ли­за­ции, долж­ны знать обо всех сво­их вир­ту­аль­ных пред­ках. Кро­ме то­го, при до­бав­лении в ие­рар­хию но­во­го про­из­вод­но­го клас­са он обя­зан при­нять на се­бя обя­за­тель­ст­во по инициа­ли­за­ции вир­ту­аль­ных ба­зо­вых клас­сов. Ко все­му про­че­му, возника­ет ре­зон­ный во­прос о спо­со­бах кор­рект­ной реа­ли­за­ции опе­ра­ций ко­пи­ро­вания и при­сваи­вания в све­те вы­ше­опи­сан­ных про­блем. Един­ст­вен­ный ра­дикаль­ный вы­ход в сло­жив­шей­ся си­туа­ции – это от­каз от по­лей дан­ных в ро­ди­тель­ских клас­сах. Та­ким об­ра­зом уст­ра­ня­ет­ся необ­хо­ди­мость в пе­ре­да­че ар­гу­мен­тов кон­ст­рук­то­рам вир­ту­аль­ных ба­зо­вых клас­сов.

На­де­юсь, те­перь яс­но­сти ста­ло боль­ше. Же­лаю­щие ра­зо­брать­ся в этом во­про­се ещё глуб­же мо­гут це­ле­на­прав­лен­но про­дол­жить свои по­ис­ки, но пе­ред этим мы по­лу­чим мак­си­мум от воз­мож­но­стей плат­фор­мы Mono. Как имен­но? Чи­та­ем даль­ше!

Что хо­тим

Ка­кую функ­цио­наль­ность нам бы хо­те­лось иметь, пусть да­же в пер­вом при­бли­жении? Итак:

  • До­черний класс, на­сле­дуя сво­им ро­ди­те­лям, дол­жен иметь воз­мож­ность вы­зы­вать их ме­то­ды без необ­хо­ди­мо­сти для про­грам­ми­ста по­втор­но пе­ре­пи­сы­вать код. Сво­бо­да ис­поль­зо­вания то­го, что уже есть.
  • Мы хо­тим иметь воз­мож­ность ис­поль­зо­вать объ­ек­ты до­чернего клас­са там, где ожи­да­ют­ся объ­ек­ты ро­ди­тель­ских клас­сов. Это по­зво­лит нам опе­ри­ро­вать про­из­вод­ным клас­сом че­рез ссыл­ки на ба­зо­вые клас­сы. По су­ти, обыч­ный по­ли­мор­физм.
  • Ро­ди­тель­ские клас­сы для реа­ли­за­ции ме­ханиз­ма на­сле­до­вания не долж­ны под­вер­гать­ся ника­ким из­менениям. Дол­жен со­блю­дать­ся прин­цип це­ло­ст­но­сти. При со­блю­дении дан­но­го тре­бо­вания мы смо­жем ис­поль­зо­вать ро­ди­тель­ские клас­сы толь­ко по­сред­ст­вом их ин­тер­фей­са, что очень ак­ту­аль­но при ра­бо­те с ди­на­ми­че­­ски­­ми биб­лио­те­ка­ми, когда ис­ход­ный код недосту­пен.

Мо­де­ли­ро­вание

В ка­че­­ст­ве пред­мет­ной об­лас­ти мы бу­дем ис­поль­зо­вать офис­ную технику: прин­те­ры, сканеры и мно­го­функ­цио­наль­ные уст­рой­ст­ва (МФУ). Ду­маю, нет смыс­ла рас­ска­зы­вать что та­кое «прин­тер» и «сканер». Что ка­са­ет­ся МФУ, то это уст­рой­ст­ва, пре­ж­де все­го, объ­е­ди­няю­щие в се­бе воз­мож­но­сти прин­те­ра и сканера, так ска­зать, «два в од­ном». Кро­ме то­го, в неко­то­рых мо­де­лях мо­жет при­сут­ст­во­вать функ­цио­наль­ность фак­са и кард-ри­де­ра. МФУ по­зво­ля­ет эко­но­мить ме­сто на сто­ле и ин­тер­фейс­ные разъ­ё­мы, а ес­ли при­сут­ст­ву­ет под­держ­ка опе­ра­ци­он­ной сис­те­мы Linux, так и во­об­ще за­ме­ча­тель­ный ап­па­рат!

Прин­те­ры и сканеры в на­шем слу­чае сим­во­ли­зи­ру­ют две неза­ви­си­мые ие­рар­хии, ко­то­рые мы хо­тим объ­е­динить с це­лью по­лу­чения но­вой сущ­но­сти – МФУ. Оче­вид­но, что класс прин­те­ров об­ла­да­ет свои­ми спе­ци­фи­че­­ски­­ми дан­ны­ми и ме­то­да­ми, а класс сканеров – свои­ми. Класс МФУ, на­сле­дуя клас­су «прин­тер» и клас­су «сканер», по­лу­чит оп­ре­де­лён­ный на­бор ме­то­дов от ро­ди­тель­ских клас­сов, и кро­ме то­го, у него бу­дут свои соб­ст­вен­ные по­ля дан­ные и ме­то­ды.

Для про­грамм­ной мо­де­ли из все­го мно­го­об­ра­зия свойств прин­те­ров мы бу­дем ис­поль­зо­вать свой­ст­во «язык опи­сания стра-ниц» (Page Description Language – PDL), а сканер бу­дет оп­ре­де­лять­ся оп­ти­че­­ским раз­ре­шением. В при­ла­гае­мом фай­ле LibDev.cs со­дер­жит­ся код клас­сов Printer и Scaner. Ти­пы дан­ных, пред­став­ляю­щие со­бой ро­ди­тель­ские клас­сы Printer и Scaner, очень про­сты. Они вынесе­ны в своё соб­ст­вен­ное про­стран­ст­во имён OfficeDevices. Дан­ный файл ком­пи­ли­ру­ет­ся в dll-биб­лио­те­ку, ко­то­рая за­тем ис­поль­зу­ет­ся в основ­ном про­ек­те.

Тип да­нных «ПРИНТЕР» со­дер­жит по­ле дан­ных page_language, свой­ст­во Page_Language с про­це­ду­ра­ми досту­па get и set, кон­ст­рук­тор с па­ра­мет­ром для инициа­ли­за­ции по­ля дан­ных и ме­тод ShowPageLanguageInfo(), вы­во­дящий на кон­соль зна­чение по­ля дан­ных. Тип дан­ных «СКАНЕР» – по­ле дан­ных resolution, свой­ст­во Resolution с про­це­ду­ра­ми досту­па get и set, кон­ст­рук­тор с па­ра­мет­ром для инициа­ли­за­ции по­ля дан­ных и ме­тод ShowResolutionInfo(), вы­во­дящий на кон­соль зна­чение по­ля дан­ных.

Класс-на­следник – MFU (сущ­ность «МНОГОФУНКЦИОНАЛЬНОЕ УСТРОЙСТВО»). Имен­но он яв­ля­ет­ся на­шей конеч­ной це­лью.

Язы­ко­вые сред­ст­ва

Ком­по­зи­ция – это от­но­шение ме­ж­ду клас­са­ми, возникаю­щее тогда, когда объ­ект од­но­го ти­па со­дер­жит в се­бе объ­ек­ты дру­гих ти­пов. В ка­че­­ст­ве си­нонимов тер­ми­на «ком­по­зи­ция» вы­сту­па­ют «вло­жение» или «аг­ре­ги­ро­вание». При­мер:

class Address {}
 class ContactInfo{}
 class Person
 {
 	 Address address;
 	 ContactInfo contact;
 }

Когда мы го­во­рим о на­сле­до­вании, то для нас это оз­на­ча­ет, что «класс яв­ля­ет­ся раз­но­вид­но­стью дру­го­го клас­са». В тер­мин «ком­по­зи­ция» вкла­ды­ва­ет­ся смысл «со­дер­жит» или «реа­ли­зу­ет­ся по­сред­ст­вом».

Бы­ва­ет так, что при раз­ра­бот­ке про­грамм тре­бу­ет­ся вы­полнить при­ве­дение од­но­го соз­дан­но­го про­грам­ми­стом ти­па дан­ных к дру­го­му. Для ре­шения та­кой за­да­чи в язы­ке C# пре­ду­смот­ре­ны сред­ст­ва яв­но­го и неяв­но­го пре­об­ра­зо­вания ти­пов. Ес­ли в про­грам­ме есть класс First и класс Second и нам нуж­но вы­пол­нять неяв­ное пре­об­ра­зо­вание ти­па Second к ти­пу First, то в клас­се First нуж­но объ­я­вить ме­тод при­ве­де­ния сле­дую­ще­го ви­да:

public static implicit operator First(Second s) { // не­об­хо­ди­мые дей­ст­вия}.

Фак­ти­че­ски, вы­пол­ня­ет­ся пе­ре­груз­ка опе­ра­то­ра.

Ко­ди­ро­ва­ние

Шаг пер­вый – соз­да­ние клас­сов-на­след­ни­ков Scaner_tmp и Printer_tmp.

public class Printer_tmp : Printer
 {
 	 internal MFU mfu_part;
    public Printer_tmp(string data) : base(data) { }
    static public implicit operator MFU(Printer_tmp p)
    {
 	 return p.mfu_part;
    }
 }
 
 public class Scaner_tmp : Scaner
 {
 internal MFU mfu_part;
    public Scaner_tmp(string data) : base(data) { }
    static public implicit operator MFU(Scaner_tmp s)
    {
         return s.mfu_part;
    }
 }

Бла­го­да­ря на­сле­до­ванию клас­сов Printer и Scaner че­рез них мож­но по­лу­чить доступ к функ­ци­ям ShowPageLanguageInfo() и ShowResolutionInfo(), а так­же к свой­ст­вам ро­ди­тель­ских клас­сов. Мы ви­дим, что ка­ж­дый из этих клас­сов-на­следников име­ет по­ле дан­ных mfu_part и опе­ра­тор для при­ве­дения ти­па, ко­то­рый по­зво­ля­ет пре­об­ра­зо­вать эти клас­сы к ти­пу MFU. За­чем это нуж­но? Для то­го, что­бы мог вы­пол­нять­ся сле­дую­щий код:

Printer p1 = new Printer(“PostScript”);
 Printer p2 = new Printer(“PCL6”);
 MFU p3 = new MFU(“PCL5”, “1200x1200”, “USB 1.1);
 
 Printer [] arr = {p1, p2, p3};
 foreach (Printer print_i in arr) print_i.ShowPageLanguageInfo();
 
 Printer_tmp tmp = (Printer_tmp)arr[2];
 MFU mfu = (MFU)tmp;
 mfu.Mfu_SelfTest();
 mfu.ShowPageLanguageInfo();

В мас­си­ве arr ти­па Printer со­дер­жит­ся три эле­мен­та: два из них – это ука­за­те­ли на «на­стоя­щие» прин­те­ры, а тре­тий эле­мент – ука­за­тель на объ­ект клас­са MFU, ра­бо­та с ко­то­рым ве­дёт­ся че­рез ука­за­тель на ро­ди­тель­ский класс Printer. В цик­ле foreach все эле­мен­ты мас­си­ва «счи­та­ют­ся» прин­те­ра­ми, од­на­ко в ка­кой-то мо­мент вре­мени нам мо­жет по­на­до­бить­ся ра­бо­тать с тре­тим эле­мен­том имен­но как с объ­ек­том клас­са MFU, ко­им он, соб­ст­вен­но, и яв­ля­ет­ся.

Шаг вто­рой: соз­да­ние до­чер­не­го клас­са MFU.

public class MFU
 {
 Printer_tmp printer_part;
     Scaner_tmp scaner_part;
     string interface_type;
     public string Interface
     {
             get
         {
            return interface_type;
         }
         set
         {
            interface_type = value;
         }
     }
             public string Language
     {
             get
         {
            return printer_part.Page_Language;
         }
         set
         {
            printer_part.Page_Language = value;
         }
     }
     public string Resolution
     {
         get
         {
            return scaner_part.Resolution;
         }
         set
         {
            scaner_part.Resolution = value;
         }
     }
     public MFU(string lang, string res, string iface)
     {
             printer_part = new Printer_tmp(lang);
         scaner_part = new Scaner_tmp(res);
         printer_part.mfu_part = this;
         scaner_part.mfu_part = this;
         interface_type = iface;
     }
     public void ShowPageLanguageInfo()
     {
         printer_part.ShowPageLanguageInfo( );
     }
     public void ShowResolutionInfo()
     {
 
             scaner_part.ShowResolutionInfo( );
     }
     public void Mfu_SelfTest()
     {
                        Console.WriteLine(“MFU ready to work!”);
     }
     static public implicit operator Scaner(MFU mfu)
     {
         return mfu.scaner_part;
     }
     static public implicit operator Printer(MFU mfu)
     {
         return mfu.printer_part;
     }
  }

По­ля дан­ных: interface_type – спо­соб под­клю­чения уст­рой­ст­ва к ком­пь­ю­те­ру. Да­лее идут printer_part и scaner_part – это то са­мое аг­ре­ги­ро­вание, ко­то­рое по­мо­жет нам сде­лать по­до­бие мно­же­ст­вен­но­го на­сле­до­вания. Ин­тер­фей­сы для ра­бо­ты с по­ля­ми дан­ных клас­са: Interface, Language и Resolution. Ме­то­ды: ShowPageLanguageInfo() и ShowResolutionInfo() ор­ганизу­ют доступ к ме­то­дам ро­ди­тель­ских клас­сов. Опе­ра­то­ры пре­об­ра­зо­вания ти­пов при­во­дят тип MFU к ти­пам Scaner и Printer. Имен­но бла­го­да­ря им станет воз­мо­жен код ви­да:

Printer p = new MFU(“PostScript”, “600x600”, “LAN”);
Scaner s = new MFU(“PCL6”, “1200X1200”, “USB 2.0”);

Те­перь внима­тель­но рас­смот­рим кон­ст­рук­тор клас­са MFU. Для «свя­зи с пред­ка­ми», т. е. реа­ли­за­ции стра­те­гии аг­ре­ги­ро­вания, соз­да­ют­ся объ­ек­ты Printer_tmp и Scaner_tmp. С их по­мо­щью мы по­лу­чим доступ к функ­ци­ям ShowPageLanguageInfo() и ShowResolutionInfo(), на­хо­дя­щим­ся в клас­сах Printer и Scaner.

За­тем у соз­дан­ных объ­ек­тов инициа­ли­зи­ру­ют­ся по­ля mfu_part, ссыл­кой на соз­да­вае­мый объ­ект ти­па MFU. Это бу­дет ис­поль­зо­вать­ся при опе­ра­ци­ях при­ве­дения ти­па.

На­конец, инициа­ли­зи­ру­ет­ся по­ле дан­ных interface_type. По­смот­рим, ка­к мож­но ис­поль­зо­вать по­лу­чен­ный код:

data = new List<Printer>();
 data.Add(printer1);
 data.Add(printer2);
 data.Add(printer3);
 
 foreach (Printer print_i in data)
   print_i.ShowPageLanguageInfo();
 }
 
 Вы­вод на кон­соль:
 Page language:PostScript
 Page language:SPL
 Page language:PCL6

На этом всё. Же­лаю ус­пеш­ной ра­бо­ты!

Mono: Под­клю­ча­ем биб­лио­те­ки

Пусть наш файл с име­нем ProgramLib.cs со­дер­жит не­кий ис­ход­ный код на язы­ке C#, ко­то­рый мы хо­тим пре­вра­тить в ди­на­ми­че­ски под­клю­чае­мую биб­лио­те­ку. Файл Program.cs со­дер­жит код про­грам­мы, ко­то­рый в сво­ей ра­бо­те ис­поль­зу­ет код биб­лио­те­ки ProgramLib.dll. На­би­ра­ем в кон­со­ли сле­дую­щие ко­ман­ды:

$ gmcs -t:library ProgramLib.cs
$ gmcs -r:ProgramLib.dll Program.cs

Вот та­ким не­хит­рым об­ра­зом обес­пе­чи­ва­ет­ся ра­бо­та с ди­на­ми­че­ски­ми биб­лио­те­ка­ми в ко­манд­ной стро­ке.

Личные инструменты
  • Купить электронную версию
  • Подписаться на бумажную версию