dynamic polymorphism - Софтуерен университет

dynamic polymorphism - Софтуерен университет

+ Нов въпрос
dobri19 avatar dobri19 1 Точки

dynamic polymorphism

 Някой може ли да ми разясни, ако имам абстрактен клас Animal с абстрактни методи, и съответно негов наследник Dog  с имплементация на тези методи, при dynamic polymorphism, каква е разликата и защо трябва да предпочита едната декларация Animal dog = new Dog() или другата Dog dog = new Dog() , като съм наясно че при извикаване на методите в единия случай ще имаме runtime, а при другия compiletime , и съответно първият ще е по-бавен, а резултата е един и същ?

Тагове:
0
C# OOP Basics 16/01/2017 11:33:50
ktrajkov avatar ktrajkov 4 Точки
Animal[] animals =new[] { new Dog(), new Cat()};

Това как би го направил?

0
16/01/2017 11:57:20
dobri19 avatar dobri19 1 Точки

Animal[] animals = new Animal[2];
animals[0] = new Dog();
animals[1] = new Cat();

или

List<Animal> animals = new List<Animal>();
animals.Add(new Dog());
animals.Add(new Cat());

, а обект от тип Animal не мога да добавям защото е абстрактен клас.

0
val4o89 avatar val4o89 240 Точки

На абстрактните класове не можеш да създаваш инстанция, за пример от живия живот - не съществува животното "животно", то може да бъде котка, куче (нещо конкретно).

0
RoYaL avatar RoYaL SoftUni Team Trainer 6789 Точки

Dog dog = new Dog(); - това просто не е полиморфизъм. Тук се използва method overloading, което може да бъде постигнато и просто с два метода в един клас, без да има нужда от клас родител.

1
dobri19 avatar dobri19 1 Точки

 Но абстрактния ми метод няма тяло, a на наследника ми метода е override. Така в този случай може ли да говорим за overloading (когато името на метода и сигнатурата при двата са еднакви)?

0
RoYaL avatar RoYaL SoftUni Team Trainer 6789 Точки

Ххората обикновено казват статичен полиморфизъм на друг термин, а именно на ситуация в която се получава Overloading (възможно е това да се получи косвено, когато компилаторът вземе методите от базовите класове и ги залепи). Извикването на различен метод в зависимост от сигнатурата наричат наличието на много форми. 

В твоя сценарий, който си дал е абсолютно все едно кой от двата начина ще ползваш - резултатите ще са едни и същи.

0
16/01/2017 12:55:22
dobri19 avatar dobri19 1 Точки

 Мерси, а може ли да дадеш някакъв пример при който печеля нещо от декларирането на обект през бащиния клас? (Animal dog = new Dog()     >    Dog dog = new Dog() )

0
val4o89 avatar val4o89 240 Точки

Един от плюсовете на Animal dog = new Dog() е, че после можеш да декларираш котка (Animal cat = new Cat()), куче, жираф, и т.н. , да ги вкараш в някаква колекция (примерно List<Animal>()) и чрез един форийч (или друг цикъл) да изциклиш всичките животни и на всяко едно да повикаш някой от наследените методи (примерно MakeSound(), Eat(), Walk(), Die().... и т.н.). Като цяло въпроса за абстракцията и полиморфизма е доста широка тема. За по-добро разбиране те съветвам да гледаш лекцията интерфейси и абстракция

0
16/01/2017 12:01:19
dobri19 avatar dobri19 1 Точки

Да благодаря за инфото, но точно това може да се прави и когато са декларирани през своя клас и пак ще влязат в списък с бащиния тип, който може да се обхожда и извикват техните наследени методи.

0
val4o89 avatar val4o89 240 Точки

Да, прав си, можеш, но от гледна точка на енкапсулация няма смисъл да даваш достъп до методи, пропъртита и т.н. от конкретния клас в случай, че в последствие ще ползваш тези на родителя. Зависи от сценария.

0
dobri19 avatar dobri19 1 Точки

 Тоест ако в наследения клас имам допълнителни методи и пропъртита и искам да ги използвам го декларирам  Dog dog = new Dog() ,  а ако не искам достъп до тях като Animal dog = new Dog()?(Въпреки, че щом съм ги създал в класа явно ще искам да ги ползвам).

0
dobri19 avatar dobri19 1 Точки

 Прочетох няколко от статиите за полиморфизъм от Google, и мисля че попаднах на част от отговорите които търсех. Това което аз разбрах е, че и при двата посочени от мене примера имаме полиморфизъм. В случая когато извикваме методи за Dog dog = new Dog(), имаме Compile time Polymorphism or Early Binding - Examples of early binding are overloaded methods, overloaded operators and overridden methods that are called directly by using derived objects. Тоест в този случаи имаме - overridden methods that are called directly by using derived objects. А когато извикваме методи за Animal dog = new Dog(), имаме Runtime Polymorphism or Late Binding - Example of late binding is overridden methods that are called using base class object. А относно предимството на втория начин пише - Advantage of late binding is flexibility. И така отново ми останаха въпросите, какво точно е това flexibility, и кога да ползвам Runtime Polymorphism or Late Binding?  

0
vrabeca avatar vrabeca 26 Точки

http://csharp-video-tutorials.blogspot.bg/2012/07/part-55-c-tutorial-late-binding-using.html в тая статия автора е написал че Late Binding  се използва когато първоначално не знаеш типа на класа и го взимаш с reflection или например декларираш абстрактен клас

Animal animal;

и след това според инпута инициализираш какъв тип е:

animal = new Dog(); или animal = new Cat();

0
RoYaL avatar RoYaL SoftUni Team Trainer 6789 Точки

Записът Dog dog = new Dog(); точно така написан без друг контекст няма никаква разлика от това да го напишеш Animal dog = new Dog();, ако методът в Animal е абстрактен. Ако това е целият въпрос - то отговорът е: няма предимство второто пред първото.

Обаче има няколко други ситуации, които може да са интересни.

1. При неправилно реализирана архитектура, методът в базовия клас може да не е Абстрактен или Виртуален, тогава наследнкът го скрива. При следната ситуация

class Animal
{
    public void A()
    {
        Console.WriteLine("aaa");
    }
}

class Dog : Animal
{
    public void A()
    {
        Console.WriteLine("bbb");
    }
}

Имаме следните резултати:

        Animal a = new Dog();
        a.A(); // aaa
        Dog b = new Dog();
        b.A(); // bbb

2. Не се възползваме правилно от абстракцията и естествената капсулация, която тя произвежда. Нека си представим ситуация, в която Animal е реализиран правилно с abstrac/virtual метод, но неговите наследници имат специфични за тях методи (нещо съвсем нормално):

class Animal
{
    public virtual void A()
    {
        Console.WriteLine("aaa");
    }
}

class Dog : Animal
{
    public override void A()
    {
        Console.WriteLine("bbb");
    }

    public void SpecificDogMethod()
    {
        Console.WriteLine("Specific for dog");
    }
}

class Cat : Animal
{
    public override void A()
    {
        Console.WriteLine("ccc");
    }

    public void SpecificCatMethod()
    {
        Console.WriteLine("Specific for cat");
    }
}

При запис Animal animal = new Dog(); и използване на променливата animal ние ще виждаме метода "A()" само. Което е супер, защото можем лесно да подменим new Dog(); с new Cat(); и кодът да работи.

        Animal animal = new Dog();
        animal.A(); // "bbb"
        animal.SpecifiDogMethod(); // compile error - този метод не съществува в Animal
        animal.SpecificCatMethod(); // compile error - този метод не съществува в Animal

Съответно ако имаме реда:

        Animal animal = new Dog();
        animal.A(); // "bbb"

Лесно можем да го заменим с

        Animal animal = new Cat();
        animal.A(); // "ccc"

И просто изходът ще е различен, но кодът ще работи правилно. Добре, обаче ако имаме инициализацията от първия прмер, ние преспокойно можем да извикаме специфичен за кучето метод, нали?:

        Dog animal = new Dog();
        animal.SpecificDogMethod(); // outputs: "Specific for dog"

Така обаче сме крайно вързани за методите на кучето. А вярвай ми, напишеш ли Dog animal = new Dog(); никой по-късно в кода няма да се съобрази да не ползва специфични методи за кучето, просто защото като цъкне точка ще го види в контексното меню и ще хо ползва. Така, че нещо може ли да се обърка, то вероятно ще се обърка. А ние искаме да минимизираме риска т.е. да не позволяваме на хората да объркат (затова и C# не позволява int x = 1; по-късно да му кажеш x = "pesho"; минимизира възможните грешки)

Не можем да подменим променливата animal повече с Cat, защото котката няма да има специфични за кучето методи:

        Cat animal = new Cat(); // променен е този ред
        animal.SpecificDogMethod(); // compile error - котката няма метод "SpecificDogMethod"

3. Може да гледаш със същото око и на аргументи на методи. Там имаме същия проблем. Поискаме ли като аргумент на метод дадена Куче или Котка, вместо Животно, не можем да го заменим.

4. Динамичното bind-ване обикновено реферира към настъпило динамично събитие според което се определя дали ще стане куче или котка. Ако потребителят иска да избере едно от двете, и практически нямаш шанс да му позволиш, ако променливата е от тип Куче.

        string animalType = Console.ReadLine();
        Animal animal = null;
        if (animalType == "Dog")
        {
            animal = new Dog();
        }
        else if (animalType == "Cat")
        {
            animal = new Cat();
        }
        // после извикваме някакъв метод от Животно
        // после друг, но никога специфичен за Котка/Куче
        // после например подаваме променливата на друг метод да прави нещо с нея

Това нямаше как да стане ако втория ред беше Dog animal = null;

А също така може да премахнем case-ването и съвсем динамично на база потребитебския вход да вдигнем инстанция на куче или котка, а вопследствие ако се появи заек да не трябва да пипаме кода:

        string animalType = Console.ReadLine();
        Animal animal = (Animal)Activator.CreateInstance(Type.GetType(animalType));
        animal.A();
        // после извикваме някакъв метод от Животно
        // после друг, но никога специфичен за Котка/Куче
        // после например подаваме променливата на друг метод да прави нещо с нея

Мантрата в обектно-ориентирания дизайн като цяло е, че се опитваме да пишем такъв дизайн на кода, в който с минимум промени правим максимум резултати. Ако се налага да минем през целия код и да променим всичко, за да работи "подмяната", значи не е имало нужда да си усложняваме живота с класове и обекти и можехме просто да си редим ред след ред код. Така или иначе след време тези редове щяха да се сменят с други.

 

3
16/01/2017 16:14:52
dobri19 avatar dobri19 1 Точки

Да, с първото си изречение отговори на моя въпрос за конкретния ми пример. 

Точка 1 е ясна.

Точка 3 явно е, че имаме предимство при така дефинирани методи.

Точка 2 и 4 , разбирам, че имаме по-бърза промяна, ако се наложи или да ограничим бъдещи грешки, наши или чужди в този код. Единствено да кажем за 4 точка примера, ако искаме все пак да имаме достъп до SpecificDogMethod(), и сме съзадали Animal animal = new Dog(), как ще го направим?

0
dobri19 avatar dobri19 1 Точки

 Аз сам ще си отговоря на предния въпрос :))), очевидно трябва да кастваме обекта към наследника чийто метод искаме да използваме. ((Dog)animal).SpecificDogMethod(); или да използваме is или as.

0
RoYaL avatar RoYaL SoftUni Team Trainer 6789 Точки

Да, но кастването е нещо, което ако правиш, значи правиш нещо грешно. Не би трябвало да ти се налага да кастваш. Кастът ще работи само ако преди това си инициализирал Куче. Ако си го сменил с котка - няма. Казано с по-малко думи: Не би трябвало да ти се налага да имаш достъп до специфични за конкретен клас методи, а само до такива от абстрактен клас или интерфейс.

Animal animal = new Dog();
//
//
//
//
//
((Dog)animal).SpecificDogMethod(); // works 

Но ако смениш в един момент животното да е котка - голям проблем:

Animal animal = new Cat();
//
//
//
//
//
((Dog)animal).SpecificDogMethod(); // does not work 

 

 

0
dobri19 avatar dobri19 1 Точки

 Здравейте, имам "един" доуточняващ въпрос по същата тема. Нека имаме същия пример, абстрактен клас Animal с абстрактни методи, и наследник Dog  с имплементация. Нека имаме и Animal dogBig = new Dog() и Dog dogSmall = new Dog(). Да направим и колекция List<Animal> animals = new List<Animal>(). Въпроса ми е защо компилаторът позволява и двата обекта да ги вкарам в листа? animals.Add(dogBig) и animals.Add(dogSmall). И третира ли ги по някакъв различен начин спрямо начинът им на създаване? Веднъж вкаран вътре в листа Dog dogSmall = new Dog(), става ли еквивалентен на другите обекти в листа които са инициализирани през базовия клас. И при извикване на методите при обхождане на листа компилаторът директно ли вика неговия метод или минава през базовия клас и съответно с виртуални таблици и пойнтъри търси конкретния метод както на dogBig? А когато създаваме Animal dogBig = new Dog() , компилатирът(compile-time) го третира като Animal, но всъщност знае че е Dog преди дори да викаме негови функции(run-time), но просто не ни позволява да ползваме директно методите му(compile-time), така ли е?

0
22/01/2017 10:09:33