Ищете способ скрыть базовый класс - PullRequest
1 голос
/ 22 сентября 2010

Я создаю библиотеку, в которой есть шаблон, подобный следующему:

public class Foo : Foo_Impl
{
}

public class Foo_Impl
{
}

Я не хочу, чтобы другие разработчики случайно использовали класс Foo_Impl.Какие варианты доступны для меня, чтобы скрыть это?Я также хотел бы скрыть это от других классов в той же сборке, в которой он определен. В идеале я бы хотел сделать:

public class Foo : Foo_Impl
{
  private class Foo_Impl
  {
  }
}

Но это не работает по разным причинам.

Ответы [ 7 ]

9 голосов
/ 22 сентября 2010

Я еще не видел предложенного, поэтому подумал, что добавлю свои $ .02.

Как насчет использования композиции вместо наследования?Экземпляр Foo_Impl может поддерживаться Foo и никогда не будет виден внешнему миру (потому что Foo_impl будет закрытым для сборки).Вызовы могут передаваться через интерфейс к функциям Foo_Impl по мере необходимости.Вы получаете ту же функциональность и ни одной головной боли при проектировании.

private class Foo_Impl
{
    public void DoSomething() { }
}

public class Foo
{
    private Foo_Impl _implementation;

    public Foo() { _implementation = new Foo_Impl(); }

    public void DoSomething() { _implementation.DoSomething(); }
}

Что касается «сокрытия его от других классов в сборке», вы можете сделать его вложенным классом, если действительно считаете, что это уместно.

5 голосов
/ 22 сентября 2010

Сделать Foo_Impl абстрактным классом. Это не помешает другим разработчикам извлекать уроки из него, но сделает невозможным непосредственное создание экземпляра Foo_Impl - его необходимо создать, создав производный объект, такой как Foo.

public abstract class Foo_Impl
{
    public Foo_Impl()
    {
    }
}

public class Foo : Foo_Impl
{
    public Foo() // implicitly calls the base class's constructor
    {
    }
}

-

var works = new Foo(); // Works
var error = new Foo_Impl(); // Compiler error

Согласно предложению Томаса Левеска, у вас также есть возможность сделать абстрактный конструктор внутренним:

public abstract class Foo_Impl
{
    internal Foo_Impl()
    {
    }
}

public class Foo : Foo_Impl
{
    public Foo() // implicitly calls the base class's constructor
    {
    }
}

Это не позволит разработчикам наследовать от Foo_Impl извне сборки.

4 голосов
/ 22 сентября 2010

Доступны следующие параметры:

  • Сделать Foo_Impl абстрактным.Разработчики не смогут напрямую создать экземпляр Foo_Impl;они должны объявить Foo.Однако Foo_Impl все еще можно использовать в качестве параметра метода или параметра универсального типа, и разработчики могут извлекать свои собственные классы из Foo_Impl.

  • Сделать Foo_Impl внутренним и поместить его и Foo вотделить библиотеку от любого другого класса, от которого вы хотите скрыть Foo_Impl.Разработчики, работающие вне этой сборки, не смогут увидеть Foo_Impl.Однако разработчики могут отразить тип и создать экземпляр, если они имеют достаточные разрешения CAS во время выполнения, и любой разработчик, имеющий доступ к источнику вашей сборки, может создать Foo_Impl внутри этой сборки.

  • Сделать все конструкторы Foo_Impl защищенными или внутренними.Защищенные конструкторы могут быть доступны только производным классам, в то время как внутренние конструкторы доступны только из одной сборки.В любом случае, Foo_Impl не может быть создан сторонним разработчиком.Однако разработчики могут по-прежнему использовать этот класс в качестве параметра или универсального типа.

  • Пометить конструкторы Foo_Impl или весь класс как устаревшие с помощью атрибута [Obsolete], указав, что использование этогокласс должен вызвать ошибку.Это своего рода хак, но вы можете предотвратить компиляцию «неправильного» кода и использовать параметр сообщения атрибута, чтобы указать, что СЛЕДУЕТ использовать.Тем не менее, позаботьтесь о том, что вы украшаете;если вы украсите весь класс, я не думаю, что компилятор позволит вам НИЧЕГО делать с классом, включая наследование от него.Декорирование конструкторов должно остановить большинство кодеров.

4 голосов
/ 22 сентября 2010

Нет способа полностью скрыть это.Если Foo будет публичным, его базовый класс тоже должен быть публичным.Однако вы можете запретить другим разработчикам создавать экземпляр Foo_Impl, сделав конструктор защищенным.Таким образом, конструктор можно использовать только из производных классов.

public class Foo_Impl
{
    protected Foo_Impl()
    {
    }
}

public class Foo : Foo_Impl
{
    public Foo() // implicitly calls the base class's protected constructor
    {
    }
}

Если вы не хотите, чтобы люди могли создавать свои собственные классы, производные от Foo_Impl, сделайте конструктор внутренним

3 голосов
/ 22 сентября 2010

Это может звучать глупо, но есть очень простое решение: документировать правильное использование.Если не нужно создавать экземпляр Foo_Impl, добавьте комментарий к классу, сказав это.Включите его в любую другую документацию о классе.

Вы можете (и должны) сделать это, даже если вы реализуете одно из других решений здесь.

1 голос
/ 22 сентября 2010

Ключевое слово abstract , вероятно, наиболее применимо к вашему сценарию. Имейте в виду, что если вы предоставляете сборку (dll), для которой вы хотите, чтобы люди использовали определенные функциональные возможности, вы можете реализовать парадигму black box через Interfaces . Если вы предоставляете интерфейс для использования и внутренне реализуете интерфейс, ваша сборка может предоставить ссылку на интерфейс, не передавая ссылку на сам экземплярный класс. Все, что разработчики знают, это свойства и методы, которые вы определили в определении интерфейса. Это очень распространено в архитектурах плагинов.

0 голосов
/ 22 сентября 2010

Хорошо, я думаю, что разгадал загадку.Вы реализуете Foo_Impl в двух частях.

Одна из них - это реализация public partial class Foo_Impl {}, которая по сути является пустым классом, который ничего не делает, поэтому он не будет использоваться в любом смысле.

Другой реализован как член private partial class Foo_Impl{} класса Foo:Foo_Impl.Вот где вы размещаете все свои секретные функции, которые могут использоваться только самим Foo.Любой класс может наследовать от публичной частичной части Foo_Impl, но не может использовать какую-либо частную функциональность, доступную только Foo через его личный член Foo_Impl.Пример class Voyeur:Foo_Impl используется для иллюстрации того, что каждое тело может наследовать, но только Foo может его создать и использовать.Voyeur не может заглянуть в Foo_Impl, чтобы увидеть его полезные приватные части;).

    using System;



    static class Program
    {


        static void Main(string[] args)
        {

            new Foo();
            new Voyeur();


            Console.ReadLine();

        }


    }


     public partial class Foo_Impl
    {
         // NOTHING in this public partial implementation. 
         // Visible but cannot be used for anything

        protected Foo_Impl()
        {
            Console.Write("public partial Foo_Impl's protected constructor ");
        }

    }



    public class Foo : Foo_Impl
    {
        private partial class Foo_Impl {

            // put all your secret implementations here that you dont want 
            // any other class to accidentally use (except Foo)

            public int fooAccessibleVariable=42;

            public Foo_Impl ()

            {
                Console.Write("private partial Foo_Impl contructor ");

            }



        }

        public Foo():base() // calls the public partial Foo_Impl's construtor
        {
            Console.WriteLine(" called from Foo()");


            Foo_Impl myFoo_Impl =  new Foo_Impl(); // calls the private partial Foo_Imp's constructor
            Console.WriteLine(" called from Foo()");

            Console.WriteLine("private Foo_Impl's variabe thats only accessible to Foo: {0}",
                myFoo_Impl.fooAccessibleVariable);
        }

    }

    class Voyeur:Foo_Impl
    {



        public Voyeur():base()// calls the public partial Foo_Impl's contructor 
        {

            Console.WriteLine("  called from Voyeur()");

        }


    }
...