Коли і навіщо використовувати вкладені класи?


30

Використовуючи об'єктно-орієнтоване програмування, ми маємо можливість створювати клас всередині класу (вкладений клас), але я ніколи не створював вкладений клас за 4 роки роботи з кодуванням.
Для чого хороші вкладені класи?

Я знаю, що клас може бути позначений як приватний, якщо він вкладений і що ми можемо отримати доступ до всіх приватних членів цього класу з класу, що містить. Ми могли просто розмістити змінні як приватні у самому класі, що містить.
То навіщо створювати вкладений клас?

У яких сценаріях слід використовувати вкладені класи чи вони більш потужні в плані використання над іншими методами?


1
Ви маєте кілька хороших відповідей, і я інколи просто матиму якийсь робочий клас або підказки, які мені потрібні лише всередині класу.
папараццо

Відповіді:


19

Основна особливість вкладених класів полягає в тому, що вони можуть отримати доступ до приватних членів зовнішнього класу, маючи при цьому всю потужність самого класу. Також вони можуть бути приватними, що дозволяє здійснити досить потужне капсулювання за певних обставин:

Тут ми повністю замикаємо сеттер до фабрики, оскільки клас приватний, жоден споживач не може його знищити та отримати доступ до сетера, і ми можемо повністю контролювати те, що дозволено.

public interface IFoo 
{
    int Foo{get;}      
}
public class Factory
{
    private class MyFoo : IFoo
    {
        public int Foo{get;set;}
    }
    public IFoo CreateFoo(int value) => new MyFoo{Foo = value};
}

Крім цього, це корисно для реалізації сторонніх інтерфейсів у контрольованому середовищі, де ми все ще можемо отримати доступ до приватних членів.

Якби ми, наприклад, мали надати примірник якогось інтерфейсу якомусь іншому об'єкту, але ми не хочемо, щоб наш основний клас реалізував його, ми могли б дозволити внутрішньому класу його реалізувати.

public class Outer
{
    private int _example;
    private class Inner : ISomeInterface
    {
        Outer _outer;
        public Inner(Outer outer){_outer = outer;}
        public int DoStuff() => _outer._example;
    }
    public void DoStuff(){_someDependency.DoBar(new Inner(this)); }
}

У більшості випадків я очікую, що делегати будуть більш чітким способом робити те, що ви показуєте у другому прикладі
Бен Ааронсон

@BenAaronson як би ти реалізував випадковий інтерфейс за допомогою делегатів?
Есбен Сков Педерсен

@EsbenSkovPedersen Ну для вашого прикладу, замість того, щоб передавати екземпляр Outer, ви передасте а Func<int>, що було б просто() => _example
Бен Аронсон,

@BenAaronson в цьому надзвичайно простому випадку ви праві, але для складніших прикладів занадто багато делегатів стає незграбним.
Есбен Сков Педерсен

@EsbenSkovPedersen: Ваш приклад має певні заслуги, але IMO слід застосовувати лише у випадках, коли створення Innerвкладених і internalне працює (тобто коли ви не маєте справу з різними збірками). Зростання читабельності з класів гніздування робить її менш сприятливою, ніж використання internal(де можливо).
Flater

23

Як правило, вкладений клас N створюється всередині класу C, коли C потребує використання чогось внутрішнього, який ніколи не повинен (безпосередньо) використовуватися за межами C, і з будь-якої причини, що щось має бути об'єктом нового типу, а не деяким існуючим тип.

Я вважаю, що це найчастіше відбувається при застосуванні методу, який повертає об'єкт, що реалізує деякий інтерфейс, і ми хочемо зберегти конкретний тип цього об’єкта прихованим, оскільки він не буде корисний ніде більше.

Реалізація IEnumerable є хорошим прикладом цього:

class BlobOfBusinessData: IEnumerable<BusinessDatum>
{
    public IEnumerator<BusinessDatum> GetEnumerator()
    {
         return new BusinessDatumEnumerator(...);
    }

    class BusinessDatumEnumerator: IEnumerator<BusinessDatum>
    {
        ...
    }
}

Просто ніхто не BlobOfBusinessDataзнає причин, які знають, або піклуються про конкретний BusinessDatumEnumeratorтип, тому ми можемо також тримати його всередині BlobOfBusinessData.

Це не мало бути прикладом "найкращих практик", як IEnumerableправильно реалізовувати , просто мінімум, щоб зрозуміти ідею, тому я залишив речі, як явний IEnumerable.GetEnumerator()метод.


6
Інший приклад, який я використовую з новішими програмістами, - це Nodeклас в LinkedList. Кожен, хто використовує LinkedList, не байдуже, як Nodeце реалізовано, якщо вони можуть отримати доступ до вмісту. Єдина сутність, яка взагалі дбає - це LinkedListсам клас.
Маг Сі

3

То навіщо створювати вкладений клас?

Я можу придумати кілька важливих причин:

1. Увімкніть інкапсуляцію

Багато разів вкладені класи - це деталі реалізації класу. Користувачі основного класу не повинні дбати про своє існування. Ви повинні мати можливість їх змінити за бажанням, не вимагаючи від користувачів основного класу змінювати свій код.

2. Уникайте забруднення імен

Не слід додавати типи, змінні, функції тощо у область застосування, якщо вони не підходять для цієї області. Це трохи відрізняється від інкапсуляції. Це може бути корисно розкрити інтерфейс вкладеного типу, але належне місце для вкладеного типу все ще є основним класом. На землі C ++ типи ітераторів є одним із таких прикладів. Мені не вистачає досвіду роботи в C #, щоб надати вам конкретні приклади.

Складемо спрощений приклад, щоб проілюструвати, чому переміщення вкладеного класу до тієї ж області, що і основний клас, - це забруднення імен. Скажімо, ви реалізуєте зв'язаний клас списку. Як правило, ви б використовували

publid class LinkedList
{
   class Node { ... }
   // Use Node to implement the LinkedList class.
}

Якщо ви вирішите перейти Nodeдо тих же обсягів LinkedList, що і у вас, буде

public class LinkedListNode
{
}

public class LinkedList
{
  // Use LinkedListNode to implement the class
}

LinkedListNodeнавряд чи буде корисним без LinkedListсамого класу. Навіть якщо LinkedListнадаються деякі функції, які повертають LinkedListNodeоб'єкт, яким користувач LinkedListможе користуватися, він все одно стає LinkedListNodeкорисним лише тоді, коли LinkedListвикористовується. З цієї причини перетворення класу "вузол" в одноранговий клас LinkedListзабруднює область, що містить.


0
  1. Я використовую відкриті вкладені класи для споріднених класів помічників.

    public class MyRecord {
        // stuff
        public class Comparer : IComparer<MyRecord> {
        }
        public class EqualsComparer : IEqualsComparer<MyRecord> {
        }
    }
    MyRecord[] array;
    Arrays.sort(array, new MyRecord.Comparer());
  2. Використовуйте їх для пов'язаних варіацій.

    // Class that may or may not be mutable.
    public class MyRecord {
        protected string name;
        public virtual String Name { get => name; set => throw new InvalidOperation(); }
    
        public Mutable {
            public override String { get => name; set => name = value; }
        }
    }
    
    MyRecord mutableRecord = new MyRecord.Mutable();

Абонент може вибрати, яка версія підходить для якої проблеми. Іноді клас не може бути повністю побудований за один прохід, і він вимагає змінної версії. Це завжди вірно при обробці циклічних даних. Мутабелі можуть бути перетворені для читання лише пізніше.

  1. Я використовую їх для внутрішніх записів

    public class MyClass {
        List<Line> lines = new List<Line>();
    
        public void AddUser( string name, string address ) => lines.Add(new Line { Name = name, Address = address });
    
        class Line { string Name; string Address; }
    }

0

Вкладений клас можна використовувати коли ви хочете створити більше одного разу екземпляр класу або коли ви хочете зробити цей тип більш доступним.

Вкладений клас збільшує інкапсуляцію, так як це призведе до більш читабельного та доступного коду.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.