Loading...

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

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
RoYaL avatar RoYaL Trainer 6849 Точки

Записът 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 Trainer 6849 Точки

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

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

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

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

 

 

0
dobri19 avatar dobri19 1 Точки

 Да точно така, трябва да имаме проверка с is или as преди да го кастваме. Добре, но по-нагоре спомена че е нормално наследниците да имат специфични методи ("неговите наследници имат специфични за тях методи (нещо съвсем нормално)")(да речем искаме кучето да прави кълбо, а котката не може), а сега че  "Не би трябвало да ти се налага да имаш достъп до специфични за конкретен клас методи, а само до такива от абстрактен клас или интерфейс." Така се получава, че създаваме специфични методи, а не трябва да имаме достъп до тях, тогава за какво са ни(ако всъщност ни трябват)? Благодаря за отговорите.

0
17/01/2017 19:47:12
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.