Почему создание экземпляров такое, какое оно есть?

17

Я изучал C # в течение последних шести месяцев или около того и сейчас углубляюсь в Java. Мой вопрос касается создания экземпляров (на любом языке, на самом деле), и это больше: интересно, почему они сделали это таким образом. Возьми этот пример

Person Bob = new Person();

Есть ли причина, по которой объект указан дважды? Будет ли когда-нибудь something_else Bob = new Person()?

Казалось бы, если бы я следовал из конвенции, это было бы больше похоже на:

int XIsAnInt;
Person BobIsAPerson;

Или, возможно, один из них:

Person() Bob;
new Person Bob;
new Person() Bob;
Bob = new Person();

Полагаю, мне любопытно, есть ли лучший ответ, чем «это просто так».

Джейсон Уолгемут
источник
26
Что делать, если человек является подтипом LivingThing? Вы могли бы написать LivingThing lt = new Person(). Ищите наследование и интерфейсы.
xlecoustillier
2
Person Bobобъявляет переменную типа "ссылка на Person" вызывается Bob. new Person()создает Personобъект. Ссылки, переменные и объекты - это три разные вещи!
user253751
5
Вас раздражает избыточность? Тогда почему бы не написать var bob = new Person();?
200_success
4
Person Bob();возможно в C ++ и означает почти то же самое, что иPerson Bob = Person();
user60561
3
@ user60561 нет, он объявляет функцию, не принимающую аргументов и возвращающую Person.
Николай

Ответы:

52

Будет ли когда-нибудь что-то еще, Боб = new Person ()?

Да, по наследству. Если:

public class StackExchangeMember : Person {}

Потом:

Person bob = new StackExchangeMember();
Person sam = new Person();

Боб тоже человек, и, черт возьми, он не хочет, чтобы с ним обращались иначе, чем с кем-либо еще.

Кроме того, мы могли бы наделить Боба сверхспособностями:

public interface IModerator { }
public class StackOverFlowModerator : StackExchangeMember, IModerator {}

IModerator bob = new StackOverFlowModerator();

И поэтому, ей-богу, он не потерпит никакого отношения к нему иначе, чем любой другой модератор. И он любит красться по форуму, чтобы держать всех в очереди, пока инкогнито

StackExchangeMember bob = new StackOverFlowModerator();

Затем, когда он находит какой-то плохой 1-й плакат, он сбрасывает свой плащ-невидимку и набрасывается на него.

((StackOverFlowModerator) bob).Smite(sam);

И тогда он может действовать невинно и все остальное потом:

((Person) bob).ImNotMeanIWasJustInstantiatedThatWay();
radarbob
источник
20
Это было бы намного понятнее, если бы вы указали имена объектов в нижнем регистре.
Легкость гонки с Моникой
38
Хороший пример спецификации; плохой пример наследования. Для тех, кто читает это, пожалуйста, не пытайтесь решать роли пользователей, используя наследование.
Aaronaught
8
@ Aaronaught правильно. Не создавайте отдельные классы для разных типов людей. Используйте битовое поле enum.
Коул Джонсон
1
@Aaronaught Все очень хорошо говорят, что не следует делать, но не очень полезно, не говоря, что люди должны делать вместо этого.
Pharap
5
@Pharap: я сделал именно это, в нескольких других вопросах . Ответ прост: пользователи (аутентификация / идентификация) и политика безопасности (авторизация / разрешения) должны рассматриваться как отдельные проблемы, а стандартные модели политики безопасности основаны на ролях или утверждениях. Наследование более полезно для описания объекта, который фактически выполняет аутентификацию, например, реализацию LDAP и реализацию SQL.
Аарона
34

Давайте возьмем вашу первую строку кода и рассмотрим ее.

Person Bob = new Person();

Первый Person- это спецификация типа. В C # мы можем обойтись без этого, просто сказав

var Bob = new Person();

и компилятор определит тип переменной Bob из вызова конструктора Person().

Но вы можете написать что-то вроде этого:

IPerson Bob = new Person();

Где вы не выполняете весь API-контракт Person, а только контракт, указанный в интерфейсе IPerson.

Роберт Харви
источник
2
+1: я сделаю IPersonпример в своем коде, чтобы убедиться, что я случайно не использую какие-либо частные методы, когда пишу код, который должен быть скопирован / вставлен в другую IPersonреализацию.
Корт Аммон - Восстановить Монику
@CortAmmon Я думаю, что у вас есть опечатка, очевидно, вы имели в виду «работать с полиморфизмом», а не копировать / вставлять этот код: D
Бенджамин Грюнбаум
@BenjaminGruenbaum уверен, для некоторого определения полиморфизма ;-)
Cort Ammon - Восстановить Монику
@CortAmmon как бы вы случайно вызвали приватный метод? Конечно, вы имели в виду internal?
Коул Джонсон
@ColeJohnson в любом случае. Я написал, что размышление о конкретном случае, с которым я сталкиваюсь, privateимеет смысл: фабричные методы, которые являются частью создаваемого класса. В моей ситуации доступ к частным ценностям должен быть исключением, а не нормой. Я работаю над кодом, который надолго переживет меня. Если я делаю это таким образом, я не только сам не буду использовать какие-либо частные методы, но когда следующий разработчик копирует / вставляет это несколько десятков мест, а разработчик копирует их, это уменьшает вероятность того, что кто-то увидит возможность используйте приватные методы как "нормальное" поведение.
Cort Ammon - Восстановить Монику
21
  1. Этот синтаксис в значительной степени унаследован от C ++, который, кстати, имеет оба:

    Person Bob;

    и

    Person *bob = new Bob();

    Первый для создания объекта в текущей области, второй для создания указателя на динамический объект.

  2. Вы определенно можете иметь something_else Bob = new Person()

    IEnumerable<int> nums = new List<int>(){1,2,3,4}

    Здесь вы делаете две разные вещи, указывая тип локальной переменной numsи говорите, что хотите создать новый объект типа «Список» и поместить его туда.

  3. C # вроде с вами согласен, потому что большую часть времени тип переменной идентичен тому, что вы в нее вставили, следовательно:

    var nums = new List<int>();
  4. В некоторых языках вы стараетесь не указывать типы переменных, как в F # :

    let list123 = [ 1; 2; 3 ]
AK_
источник
5
Вероятно, правильнее будет сказать, что ваш второй пример создает указатель на новый объект Bob. То, как вещи хранятся, технически является деталью реализации.
Роберт Харви
4
«Первый для создания локального объекта в стеке, второй для создания объекта в куче». О человеке, не эта дезинформация снова.
Легкость гонки с Моникой
@LightnessRacesinOrbit лучше?
AK_
1
@AK_ Хотя «динамический» в значительной степени означает «куча» (когда куча - это модель памяти, используемая платформой), «автоматический» очень отличается от «стека». Если вы это сделаете new std::pair<int, char>(), то у членов firstи secondпары будет автоматическая продолжительность хранения, но они, вероятно, будут распределены в куче (как члены объекта dynamic-storage-duration pair).
Восстановить Монику
1
@AK_: Да, эти имена подразумевают именно то, что они имеют в виду, тогда как бессмыслица "стек" против "кучи" - нет . Вот и весь смысл. То, что вы находите их в замешательстве, только усиливает необходимость их обучения, поэтому вы не полагаетесь на неточную / неправильную терминологию только потому, что она знакома!
Легкость гонки с Моникой
3

Существует огромная разница между int xи Person bob. intЭто intявляется intи он всегда должен быть intи не может быть ничем иным, как int. Даже если вы не инициализируете, intкогда объявляете его ( int x;), он все равно intустановлен на значение по умолчанию.

Однако, когда вы объявляете Person bob, существует большая гибкость в отношении того, к чему на bobсамом деле может относиться имя в любой момент времени. Он может ссылаться на a Personили ссылаться на некоторый другой класс, например Programmer, производный от Person; это может даже быть null, не ссылаясь ни на какой объект вообще.

Например:

  Person bob   = null;
  Person carol = new Person();
  Person ted   = new Programmer();
  Person alice = personFactory.functionThatReturnsSomeKindOfPersonOrNull();

Разработчики языка, безусловно, могли бы создать альтернативный синтаксис, который выполнял бы то же самое, что и Person carol = new Person()в меньшем количестве символов, но они все равно должны были бы разрешить Person carol = new Person() (или сделать какое-то странное правило, делающее этот конкретный один из четырех примеров выше недопустимым). Их больше заботило сохранение языка «простым», чем написание чрезвычайно лаконичного кода. Это, возможно, повлияло на их решение не предоставлять более короткий альтернативный синтаксис, но в любом случае это не было необходимо, и они не предоставили его.

Дэвид К
источник
1

Эти две декларации могут быть разными, но часто одинаковыми. Общий рекомендуемый шаблон в Java выглядит следующим образом:

List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();

Эти переменные listи mapобъявляются с использованием интерфейсов Listи в Mapто время как код создает конкретные реализации. Таким образом, остальная часть кода зависит только от интерфейсов, и легко выбрать другие классы реализации для создания экземпляров, например TreeMap, так как остальная часть кода не может зависеть от какой-либо части HashMapAPI, которая находится за пределамиMap интерфейса.

Другой пример, в котором два типа различаются, - это метод фабрики, который выбирает конкретный подкласс для создания экземпляра, а затем возвращает его в качестве базового типа, так что вызывающей стороне не нужно знать подробности реализации, например, выбор «политики».

Вывод типа может исправить избыточность исходного кода. Например, на Яве

List<String> listOne = Collections.emptyList();

создаст правильный вид List благодаря выводу типа и объявлению

static <T> List<T> emptyList(); 

В некоторых языках вывод типов идет дальше, например, в C ++

auto p = new Person();
Jerry101
источник
Кстати, язык Java устанавливает строгое соглашение об использовании имен идентификаторов в нижнем регистре, например bob, нет Bob. Это позволяет избежать большой неоднозначности, например, package.Class vs. Class.variable.
Jerry101
... и вот почему у вас есть clazz.
CVn
Нет, clazzиспользуется потому, что classявляется ключевым словом, поэтому его нельзя использовать в качестве идентификатора.
Jerry101
... что не было бы проблемой, если бы соглашения об именах были не такими, как они есть. Classэто совершенно правильный идентификатор.
Чао
... как есть cLaSs, cLASSи cLASs.
el.pescado
1

По словам непрофессионала:

  • Отделение объявления от экземпляра помогает отделить тех, кто использует объекты, от тех, кто их создает
  • Когда вы это делаете, полипорфизм включается, поскольку, пока созданный тип является подтипом типа объявления, весь код, использующий переменную, будет работать
  • В строго типизированных языках вы должны объявить переменную с указанием ее типа, просто var = new Process()не объявляя переменную первой.
Тулаинс Кордова
источник
0

Это также об уровне контроля над тем, что происходит. Если объявление объекта / переменной автоматически вызывает конструктор, например, если

Person somePerson;

был автоматически такой же, как

Person somePerson = new Person(blah, blah..);

тогда вы никогда не сможете использовать (например) статические фабричные методы для создания экземпляров объектов, а не конструкторов по умолчанию, то есть бывают ситуации, когда вы не хотите вызывать конструктор для нового экземпляра объекта.

Этот пример объясняется в книге « Эффективная Java» Джошуа Блоха (пункт 1 достаточно иронически!)

Дэвид Шолфилд
источник
Что касается книг, то и моя книга на C #, и книга на Java были от Джойса Фаррелла. Это именно то, что указано в курсе. Я также дополнял оба видео с YouTube на C # и Java.
Джейсон Уолгемут,
Я не критиковал, просто давал ссылку на то, откуда это пришло :)
Дэвид Шолфилд