Вложенные классы и интерфейсы

(Мне очень трудно было придумать хорошее название для этого вопроса, если кто-то хочет помочь с этим ..)

Так что у меня проблема с дизайном чего-то. По сути, у меня есть класс A, который состоит из массива объектов типа B. Я хочу, чтобы был открыт только интерфейс класса A, и хочу, чтобы класс B был по существу скрыт для любого пользователя. Я хочу иметь возможность выполнять операции с типом B и его данными, но только через интерфейс / методы класса A, вызывающие методы экземпляра B. Сложность заключается в том, что я хочу создать метод, выполняющий операции с членами. типа B, но я хотел реализовать интерфейс, а затем иметь класс, реализующий этот интерфейс, потому что я хочу, чтобы мой пользователь мог создавать свою собственную реализацию этого метода. Я думал о том, чтобы сделать что-то вроде:

public class A 
{
    B[] arr;
    C c;

    public A(C c) 
    { 
        arr = new B[100];
        this.c = c;
    }


    public void method1() 
    {
        var b = new B();
        b.someMethodofb(c);    // pass c to the method of b
    }

    private class B 
    {
        someMethodOfb(C c) 
        {
        }
    }
}

public class C : Interface1 
{
    public void method(B b) 
    {    
        //interface method we have implemented
    }
}

Я сделал класс B частным, потому что я хочу, чтобы класс A был общедоступным, чтобы все, что происходит с классом B, происходило через класс A, поэтому я вложил B ​​в A. Но поскольку класс B является частным, смогу ли я использовать его как параметр для метода моего класса C? Реализованный метод Interface1 повлияет на внутреннюю реализацию того, как B выполняет someMethodOfb, поэтому я думаю, что мне нужно передать его, чтобы иметь возможность поддерживать скрытую природу класса B. спроектировать это и достичь целей, которые я изложил в первом абзаце?

Итак, вы хотите, чтобы B был приватным, но вам также нужен интерфейс, открывающий доступ к method(B b) миру? Эти 2 требования противоречат друг другу.

pmcilreavy 20.04.2018 00:00

Вы столкнулись с проблемой X / Y? meta.stackexchange.com/questions/66377/what-is-the-xy-proble‌ м .... Вы можете объяснить вариант использования - может быть, вам нужен другой подход?

Charleh 20.04.2018 00:01

@Charleh, это связано с работой, и я не уверен, сколько подробностей я могу дать. По сути, у меня есть класс A, содержащий массив типа B. Мне также нужен метод, который выполняет операции с типом B, но мне нужно позволить пользователю реализовать свою собственную версию этого метода. Думаю, я мог бы сделать этот метод методом типа B, но я хотел ограничить, насколько уязвимым является мир типа B.

FrostyStraw 20.04.2018 00:04

Вместо того, чтобы предоставлять тип B в интерфейсе, вам необходимо предоставить общедоступный API, который вы можете перенести на тип B в методе типа A, который вы контролируете.

NetMage 20.04.2018 00:13

@FrostyStraw, некоторое время назад у меня было почти такое же требование, взгляните на этот вопрос, это действительно мне очень помогло

taquion 20.04.2018 00:14

Короткое объявление =). Я реализовал решение для сценария, в котором ваш вложенный класс является общим, это отвечать

taquion 20.04.2018 00:15

В основном у вас есть общедоступный вложенный класс, но с частным конструктором =)

taquion 20.04.2018 00:17

@pmcilreavy а что, если я сделаю только конструктор B закрытым (пока класс останется открытым)? Может ли метод (B b) по-прежнему быть представлен миру?

FrostyStraw 20.04.2018 00:32

@NetMage Я не уверен, что понимаю, что вы имеете в виду. Вы могли бы привести какие-нибудь примеры?

FrostyStraw 20.04.2018 00:32

@taquion, спасибо, это выглядит многообещающе, я посмотрю, смогу ли я сделать с ним то, что мне нужно

FrostyStraw 20.04.2018 00:33

Не уверен, что это поможет: возможно, B реализует интерфейс, содержащий someMethodOfB (c). У вас все еще может быть B как вложенный класс, но C.method () принимает интерфейс B вместо конкретного B.

ylax 20.04.2018 00:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
11
1 642
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я бы посоветовал вам добавить еще один интерфейс для публично известной стороны B, чтобы B реализовал этот интерфейс и чтобы методы C использовали этот интерфейс.

public interface IC {
    void method(IB b);
}

public interface IB {
    int Priority { get; set; }
    int Urgency { get; set; }
}

public class A {
    B[] arr;
    IC c;

    public A(C c) {
        arr = new B[100];
        this.c = c;
    }


    public void method1() {
        var r = (new Random()).Next(100);
        arr[r].someMethodOfB(c);    // pass c to the method of b
    }

    private class B : IB {
        public int Priority { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
        public int Urgency { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

        internal void someMethodOfB(IC aC) {
            aC.method(this);
            throw new NotImplementedException();
        }
    }
}

public class C : IC { // user implements
    public void method(IB b) {
        if (b.Priority > 10 || b.Urgency > 10)
            ; // do something with BI using b
        throw new NotImplementedException();
    }
}

Теперь пользователю классов необходимо знать IC, чтобы они могли создавать C, и им нужно знать IB, чтобы они могли написать тело методов в C, но им не нужно знать все B или иметь доступ к B.

@IanMercer Правильное именование оставлено в качестве упражнения для читателя (хотя я перешел на префикс с I).

NetMage 20.04.2018 01:03

Я также настоятельно рекомендую вам рассмотреть возможность изменения B [] arr на IB []. Чем меньше связаны эти классы, тем легче вам будет писать тестовые примеры для A. A не должно заботиться о том, как B реализован, просто о том, что он имеет ожидаемые свойства и методы. Так же должно быть приятно работать с Fake B.

Adam G 20.04.2018 05:37

@AdamG Я не согласен. Дело в том, что IB является публичным интерфейсом для B, но что A имеет доступ к частной стороне B - в противном случае частной стороны нет, и в чем смысл?

NetMage 20.04.2018 18:49

@NetMage, дело в том, что ваши классы должны зависеть от абстракций, а не от конкреций. Это включает A. При тестировании поведения A вам может потребоваться ввести некоторые поддельные объекты B, которые вызывают поведение в A. Сложный тип со своими собственными внутренними проверками / поведениями может сделать такие угловые случаи очень трудными для тестирования. Это менее важно для простого класса B, такого как dto. Я должен здесь провести различие. IB, который вы публикуете публично, может отличаться от интерфейса, используемого вашим классом в определении массива, и ограничивать его, позволяя вашему классу получить доступ к методам, которые не могут видеть третьи стороны.

Adam G 21.04.2018 13:54

@AdamG Где IString? Если вы напишете интерфейс для каждого класса в своих проектах, я могу сделать предложение, которое повысит вашу продуктивность.

NetMage 23.04.2018 19:55

Reductio ad adurdum. Мы не говорим о строках, иначе OP не нуждался бы в интерфейсе. B, очевидно, ведет себя нежелательно разоблачать. Строки неизменны. Меня не волнует, использовали ли они string.Join, чтобы создать тот, который они передали. Рассмотрим класс ProductDispatcher. При тестировании вы хотите иметь дело с созданием конкретного объекта клиента и добавлением счетов за просрочку? Затем вам нужны настоящие продукты, производители и т. д. Ваши тесты ProductDispatcher терпят неудачу каждый раз, когда меняются другие классы. Нет, вам просто нужно, чтобы ICustomer ответил InArrears. Это улучшает мою продуктивность.

Adam G 24.04.2018 02:14

Возьмем конкретные примеры :)

Скажем, у нас есть три класса: Customer, Order и OrderProcessor. Customer и Order - это сущности, представляющие клиента и заказ соответственно, а OrderProcessor будет обрабатывать заказ:

public interface IOrderProcessor
{
    void ProcessOrder(IOrder order);
}

public interface IOrder
{
    void FinalizeSelf(IOrderProcessor oProc);
    int CustomerId {get; set;}
}

public class Customer
{
    List<IOrder> _orders;
    IOrderProcessor _oProc;
    int _id;

    public Customer(IOrderProcessor oProc, int CustId)
    {
        _oProc = oProc;
        _orders = new List<IOrder>();
        _id = CustId;
    }

    public void CreateNewOrder()
    {
        IOrder _order = new Order() { CustomerId = _id };
        _order.FinalizeSelf(_oProc);
        _orders.Add(_order);
    }

    private class Order : IOrder
    {
        public int CustomerId {get; set;}
        public void FinalizeSelf(IOrderProcessor oProcessor)
        {
            oProcessor.ProcessOrder(this);
        }
    }
}
public class ConcreteProcessor : IOrderProcessor
{
    public void ProcessOrder(IOrder order)
    {
        //Do something
    }
}

Другие вопросы по теме