Привет всем!
В продолжение темы Ссылка на класс или делегат, где уважаемый @Alex Krass пытался
мне объяснить, какая польза от применения интерфейса.
Для меня все так же остается загадкой. Я все так же считаю, что это лишняя писанина кода.
За это время я почитал немало статей про интерфейс и просмотрел видеокурс.
Но так и не разобрался. Честно сказать, я не знаю, почему я зациклился на нем, ведь
не знал бы о его существовании, давно бы закончил писать свой проект.
Вот, к примеру, Алекс объясняет, что в его примере он с легкостью может добавить
еще 100 классов useConnect, но, на мой взгляд, ему также нужно 100 раз вызывать connect
= new UseConnect(ссылка на класс());, причем в его конкретном случае ему придется плодить
switch, чтобы вызвать все классы. Причем я так и не понял, как в таком случае можно
добраться до метода из какого-либо класса, в том примере их можно вызвать только все
кучей. Причем если в главном классе мне нужно будет еще раз использовать какой-нибудь
метод, то мне опять придется переписывать connect = new UseConnect(ссылка на класс());.
В моем же случае, где я просто пишу ссылку на класс и через переменную вызываю метод,
так я эту переменную могу использовать где захочу и когда захочу, причем могу вызвать
любой из методов. Да и вообще мне
не придется писать класс UseConnect, я могу вызывать методы сразу, в данном примере
из класса MySQL, к примеру.
Вот реальный пример, который мне нужно реализовать в своем примере. Опять же повторюсь,
с помощью ссылки на классы я уже эту задачу решал. Просто хочу посмотреть, как решают
такую задачу профи, с помощью интерфейсов.
Вот пример. У меня есть форма, в которой пользователь может сохранять записи в базе
данных. В моем случае MySQL. Он может внести запись, после вызвать ее и сделать изменения.
У меня есть пять классов. Главный, который вызывает форму, класс инсерт, класс упдата,
класс селест и класс подключения к базе. Класс инсерт упдата и селект наследуют класс
подключения к базе. Ну это я так решил сделать, чтобы не использовать интерфейс. Вот
и сама задача: как мне эти четыре класса с помощью интефейса(ов) подключить к главному
классу с формой.
Ну, я это так для моего примера взял.
Лучше, конечно, приведите, явный пример, где интерфейс облегчает жизнь программисту.
Прошу не заводить тему моей грамотности и знания русского языка. Эту тему мы уже
обсуждали и я не раз говорил о причине моей безграмотности русского языка. И так же
не посылайте меня за книжкой, ну если только посоветуете хорошую.
Ответы
Ответ 1
Сейчас Вам, скорее всего, это не надо, Вы столкнетесь с этим когда-нибудь позже,
когда будете готовы. Не заморачивайтесь и идите дальше, очень часто программисты вынуждены
пропускать темы и возвращаться к ним. Главное, что Вы знаете, что есть такая вещь,
как интерфейс, и она позволяет скрывать реальные реализации классов.
С помощью интерфейсов Вы:
не завязываете логику своего приложения с конкретными классами и можете легко вносить
изменения в одни классы, не трогая другие, добавлять функциональность или заменить
один класс абсолютно другим, не трогая все приложение. То есть реализуется абстракция
от реализации;
можете проектировать логику приложений на интерфейсах, оставив реализацию на потом
и применяя заглушки;
применять так называемое множественное наследование;
если будете начальником, сможете легко донести свои мысли до подчиненных (реализуй
мне функциональность вот этого интерфейса).
Я дам несколько примеров, в которых применяется интерфейс, но сразу оговорюсь, они
не являются лучшими представителями. Ведь я тоже учусь. )
Проектирование недозоопарка
Давайте запроектируем небольшую программу зоопарка, при этом зоопарк у нас бужет
пошаговым.
1. В первую очередь его стоит населить животными, которые умеют ходить по нему, приступим.
Для описания жизненного цикла зоопарка я использую класс ZooLivecycle:
class ZooLivecycle
{
public void allPetWalk()
{
}
}
Итак, у нас есть животные, которые умеют ходить, давайте определим их возможности
через интерфейсы и реализуем.
interface IWalk
{
void walk();
}
class Cat : IWalk
{
public void walk()
{
Console.WriteLine("cat walk");
}
}
class Dog : IWalk
{
public void walk()
{
Console.WriteLine("dog walk");
}
}
class Fish : IWalk
{
public void walk()
{
Console.WriteLine("fish cant walk");
}
}
class Bird : IWalk
{
public void walk()
{
Console.WriteLine("bird can fly");
}
}
Для того чтобы все животные ходили, проще их передать в класс зоопарка всем массивом
и пройтись по ним циклом.
class ZooLivecycle
{
public void allPetWalk(IWalk[] arr)
{
for (int i = 0; i < arr.Count(); i++)
arr[i].walk();
}
}
Итак, теперь можно вызывать нашу реализацию.
class Program
{
static void Main(string[] args)
{
ZooLivecycle zoo = new ZooLivecycle();
IWalk[] walks = new IWalk[]{new Cat(), new Dog(), new Fish(), new Bird()};
Console.WriteLine("\n*Pet Walk*");
zoo.allPetWalk(walks);
Console.Read();
}
}
А теперь попробуйте реализовать это без интерфейсов, главным образом функцию allPetWalk,
хотя есть способ - это общий класс родитель. Можно также попробовать передавать ссылку
как public void allPetWalk(Object[] arr) и потом приводить типы, но это является очень
плохой практикой, так как вы теряете контроль над типами и вынуждены добавлять проверки,
что не является лучшим решением.
2. Идем дальше, мы решили добавить систему наблюдения, а именно видеокамеры, которая
сможет передавать информацию.
interface IObservation
{
void analyze();
}
class Videocamera : IObservation
{
public void analyze()
{
Console.WriteLine("In zoo all ok");
}
}
Реализуем получение информации в классе зоопарка:
class ZooLivecycle
{
public void allPetWalk(IWalk[] arr)
{
for (int i = 0; i < arr.Count(); i++)
arr[i].walk();
}
public void zooAnalyze(IObservation[] arr)
{
for (int i = 0; i < arr.Count(); i++)
arr[i].analyze();
}
}
Ну и вызов.
class Program
{
static void Main(string[] args)
{
ZooLivecycle zoo = new ZooLivecycle();
IWalk[] walks = new IWalk[]{new Cat(), new Dog(), new Fish(), new Bird()};
IObservation[] analyze = new IObservation[] { new Videocamera() };
Console.WriteLine("\n*Pet Walk*");
zoo.allPetWalk(walks);
Console.WriteLine("\n*Analyze zoo*");
zoo.zooAnalyze(analyze);
Console.Read();
}
}
3. А теперь давайте подложим свинью, если кто-то все еще использовал класс родитель,
вместо интерфейсов.
Реализуем помошника, который может как ходить по зоопарку, так и передавать информацию.
В языке C# нет перекрестного наследования, но зато можно наследовать несколько интерфейсов
вместо этого.
class HelperPet : IWalk, IObservation
{
public void walk()
{
Console.WriteLine("helppet walk");
}
public void analyze()
{
Console.WriteLine("helppet analyze");
}
}
Это все, что пришлось добавить. Можно дальше пользоваться нашим зоопарком, ничего
не переписывая.
class Program
{
static void Main(string[] args)
{
ZooLivecycle zoo = new ZooLivecycle();
IWalk[] walks = new IWalk[]{new Cat(), new Dog(), new Fish(), new Bird(),
new HelperPet()};
IObservation[] analyze = new IObservation[] { new Videocamera(), new HelperPet() };
Console.WriteLine("\n*Pet Walk*");
zoo.allPetWalk(walks);
Console.WriteLine("\n*Analyze zoo*");
zoo.zooAnalyze(analyze);
Console.Read();
}
}
Теперь давайте рассмотрим реальные примеры, которые очень часто используются.
Очень часто интерфейсы используются как описательные модели, наследование от которых
дает возможность работать с тем, для чего они созданы. Тем самым вы не привязываетесь
к конкретным классам. В самом языке очень C# даже есть интерфейсы, которые реализуют
то или иное поведение. Вы очень часто будете с этим сталкиваться, если перейдете на
WPF или ASP.NET MVC приложения.
Использование foreach с собственными классами
Давайте улучшим наш зоопарк и добавим класс для хранения животных. Несмотря на то,
что он довольно объемный, я просто взял с MSDN пример и просто подставил свои значения.
public interface IWalk
{
void walk();
}
public class Animals : IEnumerable
{
private IWalk[] _pets;
public Animals(IWalk[] pArray)
{
_pets = new IWalk[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_pets[i] = pArray[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
public PetsEnum GetEnumerator()
{
return new PetsEnum(_pets);
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
}
public class PetsEnum : IEnumerator
{
public IWalk[] _pets;
int position = -1;
public PetsEnum(IWalk[] list)
{
_pets = list;
}
public bool MoveNext()
{
position++;
return (position < _pets.Length);
}
public void Reset()
{
position = -1;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public IWalk Current
{
get
{
try
{
return _pets[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
Теперь я могу делать так:
class Program
{
static void Main(string[] args)
{
ZooLivecycle zoo = new ZooLivecycle();
Animals animals = new Animals(new IWalk[]{new Cat(), new Dog(), new Fish(),
new Bird(), new HelperPet()});
Console.WriteLine("\n*Pet Walk*");
zoo.allPetWalk(animals);
Console.Read();
}
}
class ZooLivecycle
{
public void allPetWalk(Animals animals)
{
foreach(IWalk animal in animals)
animal.walk();
}
}
Не реализовав интерфейс, вы не сможете использовать с вашим классом foreach, хотя
на самом деле это делается очень быстро. При этом метод foreach работает с любыми объектами,
и не будь интерфейсов, достигнуть такого эффекта было бы сложнее. Теперь в класс Animals
можно добавить такие методы, как Add, Remove, etc.
Ну и в конце концов вы можете ставить заглушки и передавать тестовые данные.
Если реализация классов достаточно сложна, например, один из них получает данные
из базы, а другой обрабатывает эти данные, то вы можете написать ненастоящий класс.
То есть вместо реальных данных Вы передаете класс, которые не использует подключение
к БД, и потом его можно будет переписать. Причем можно работать нормально как с одним,
так и с другим классом попеременно.