Чому створення екземпляра таким, яким воно є?


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об’єкт. Посилання, змінні та об'єкти - це три різні речі!
користувач253751

5
Вас дратує надмірність? Тоді чому б не написати var bob = new Person();?
200_успіх

4
Person Bob();можливо на C ++ і означає майже те саме, що іPerson Bob = Person();
user60561

3
@ user60561 ні, він оголошує функцію, яка не бере аргументів, і повертає Person.
Микола

Відповіді:


52

Чи буде колись щось_ельс Боб = нова особа ()?

Так, через спадщину. Якщо:

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();

20
Це було б набагато зрозуміліше, якби ви малите накладні літери назви своїх об'єктів.
Гонки легкості з Монікою

38
Хороший приклад специфікації; поганий приклад успадкування. Комусь, хто читає це, будь ласка, не намагайтеся вирішувати ролі користувачів за допомогою спадкування.
Aaronaught

8
@Aaronaught правильно. Не створюйте окремих класів для різних типів людей. Використовуйте бітфілд enum.
Коул Джонсон

1
@Aaronaught Все це дуже добре говорить, що не робити, але це не дуже корисно, не кажучи, що люди повинні робити замість цього.
Фарап

5
@Pharap: Я зробив саме це в кількох інших питаннях . Проста відповідь полягає в тому, що користувачі (автентифікація / ідентифікація) та політика безпеки (авторизація / дозволи) повинні розглядатися як окремі проблеми, а стандартні моделі політики безпеки - або рольові, або претензії. Спадкування корисніше описати об'єкт, який фактично робить автентифікацію, наприклад, реалізацію LDAP та реалізацію SQL.
Aaronaught

34

Давайте візьмемо ваш перший рядок коду та вивчимо його.

Person Bob = new Person();

Перший Person- специфікація типу. У C # ми можемо не обійтися цим простою думкою

var Bob = new Person();

і компілятор виводити тип змінного Бобу від виклику конструктора Person().

Але ви можете написати щось подібне:

IPerson Bob = new Person();

Якщо ви не виконуєте весь контракт API на Person, а лише контракт, визначений інтерфейсом IPerson.


2
+1: Я зроблю IPersonприклад у своєму коді, щоб переконатися, що я випадково не використовую приватних методів, коли пишу код, який слід копіювати / вставляти в іншу IPersonреалізацію.
Корт Аммон - Відновіть Моніку

@CortAmmon Я думаю, що у вас там є друкарська помилка, явно ви мали на увазі "роботу з поліморфізмом", а не копіювати / вставляти цей код: D
Бенджамін Груенбаум

@ BenjaminGruenbaum впевнений, для деякого визначення поліморфізму ;-)
Корт Аммон - Відновіть Моніку

@CortAmmon, як би ви випадково викликали приватний метод? Звичайно, ви мали на увазі internal?
Коул Джонсон

@ColeJohnson в будь-якому випадку. Я написав, що роздумуючи про конкретний випадок, який я натрапляю на те, де privateє сенс: фабричні методи, що входять до класу, що інстанціюються. У моїй ситуації доступ до приватних цінностей повинен бути винятком, а не нормою. Я працюю над кодом, який довго мене переживе. Якщо я це роблю таким чином, не тільки я навряд чи сам використовую будь-які приватні методи, але коли наступний розробник копіює / вставляє це кілька десятків місць, а розробник після цього копіює їх, це зменшує шанси, що хтось бачить можливість використовувати приватні методи як "нормальну" поведінку.
Корт Аммон - Відновіть Моніку

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 ]

5
Напевно, точніше сказати, що ваш другий приклад створює вказівник на новий об’єкт Bob. Те, як зберігаються речі, технічно є деталізацією реалізації.
Роберт Харві

4
"Перший для створення локального об'єкта на стеці, другий для створення об'єкта на купі." О людино, не знову це дезінформація.
Гонки легкості з Монікою

@LightnessRacesinOrbit краще?
AK_

1
@AK_ Хоча "динамічний" в значній мірі означає "купу" (коли купа - це модель пам'яті, яка використовується платформою), "автоматичний" дуже відрізняється від "стека". Якщо це так new std::pair<int, char>(), то члени firstта secondпара мають автоматичну тривалість зберігання, але вони, ймовірно, виділяються на купі (як члени об'єкта динамічного зберігання 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інтерфейсом.

Інший приклад, коли два типи різняться, - це заводський метод, який вибирає певний підклас для екземпляру, а потім повертає його як базовий тип, тому абоненту не потрібно знати про деталі реалізації, наприклад, вибір "політики".

Висновок типу може виправити надмірність вихідного коду. Наприклад, на Java

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

створить правильний вид списку завдяки виводу типу та декларації

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

У деяких мовах висновок типу йде далі, наприклад, у C ++

auto p = new Person();

BTW мова Java встановлює чітку умову для використання малих імен ідентифікаторів, наприклад bob, ні Bob. Це дозволяє уникнути великої неоднозначності, наприклад, пакет. Клас проти класу.змінний.
Jerry101

... і саме тому у вас є clazz.
CVn

Ні, clazzвикористовується, оскільки classце ключове слово, тому його не можна використовувати як ідентифікатор.
Джері101

... що не було б проблемою, якби іменування конвенцій було не таким, як вони є. Classє абсолютно дійсним ідентифікатором.
cHao

... як є cLaSs, cLASSі cLASs.
el.pescado

1

Словами мирянина:

  • Відокремлення декларації від інстанції допомагає відокремити, хто використовує об'єкти, від того, хто їх створює
  • Коли ви це зробите, поліпорфізм увімкнено, оскільки, поки інстанційований тип є підтипом типу декларації, весь код із використанням змінної буде працювати
  • У сильно набраних мовах ви повинні оголосити змінну із зазначенням її типу, просто зробивши, var = new Process()ви не оголошуєте змінну спочатку.

0

Йдеться також про рівень контролю над тим, що відбувається. Якщо оголошення об’єкта / змінної автоматично викликає конструктор, наприклад, якщо

Person somePerson;

автоматично було таким же, як

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

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

Цей приклад пояснюється в Joshua Bloch «s Effective Java (Пункт 1 досить іронічно!)


Що стосується книг, і моя C #, і книга Java були від Джойса Фаррелла. Це саме те, що вказав курс. Я також доповнював обидва відео YouTube на C # та Java.
Jason Wohlgemuth

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