Скрытые поля C# недоступны за пределами класса – ясно как дважды два. Поэтому следующий код не должен работать.
Но, как ни странно, он работает.
Оказывается, к переменной Private можно получить доступ из другого объекта того же типа. И, если задуматься, это вполне логично. Зачем нужны переменные Private? Для инкапсуляции и управления состоянием объекта.
Представьте, что нам нужно создать контейнер с ограниченной емкостью – коробку конфет. Мы могли бы сделать это так:
Догадайтесь, в чем проблема
Несмотря на то, что у нас есть метод IsValid, в коробку можно добавить больше, чем максимальное количество конфет (хотя, думаю, заказчик был бы не против). Чтобы решить эту проблему, мы можем инкапсулировать коробку и добавить методы для соблюдения необходимых ограничений. К тому же это позволит нам избавиться от метода IsValid.
Но здесь есть одно условие.
Разработчики, создающие другие части системы, могут не догадываться, как работает ваша «коробка». В таком случае на помощь приходит модульное или интеграционное тестирование. Чтобы избежать любых недоразумений с кодом, нужно просто сделать неявное явным.
Но если разработчик знаком с классом, ему можно доверить изменение внутреннего состояния – в том числе для других классов того же типа.
Уверен, что из этой особенности можно извлечь массу плюсов, но хочу выделить лишь один, который оказался для меня особенно полезным.
Доступ к скрытым переменным при создании объектов-значений
Объекты-значения – это один из шаблонов проблемно-ориентированного программирования, который также можно использовать за пределами этой модели. Отличительная черта таких объектов в том, что их равенство основывается не на идентификаторе, а на значении.
Простой пример – строка. Две строки с одними и теми же символами в одинаковом порядке считаются равнозначными. Аналогично можно сравнивать телефонные номера из системы CRM, если, конечно, вы не пишете ПО для оператора телефонной связи – в таком случае телефонный номер может выступать идентификатором клиента и, следовательно, не быть объектом-значением.
В большинстве случаев объекты-значения неизменяемы. В том числе это касается строк: «изменяя» строку, вы, по сути, создаете новый объект. Чем полезна такая неизменяемость? Мне приходит на ум 4 причины:
• Объекты намного легче тестировать.
• Объекты легче распараллелить.
• Можно быть уверенным, что они всегда в действительном состоянии.
• Не нужно отслеживать много идентификаторов в приложении (пожалуй, основная причина).
Пример объекта-значения
В качестве примера давайте смоделируем контейнер для жидкости или, проще говоря, чашку с максимальной емкостью в 1 литр. При этом мы можем перелить содержимое из одной чашки в другую (с точки зрения кода для этого понадобится еще один экземпляр чашки). Жидкость, превышающая максимальный объем, считается утерянной. Если же две чашки содержат одинаковый объём жидкости, они равны.
Начнем с тестирования. Чтобы чашка была неизменяемой, передадим текущий объем жидкости в конструктор. Для начала проверим, можем ли мы создать чашку, наполненную жидкостью.
Теперь удостоверимся, что чашка не может содержать недопустимый объем – более одного или менее нуля литров.
А теперь самое интересное. Как сделать, чтобы чашки были одинаковыми? Для этого нам нужен доступ к скрытым переменным.
На досуге можете потренироваться с переливанием жидкости. Впрочем, возникает интересный вопрос: как перелить жидкость из чашки, если она неизменяема? :)
Вывод
Объекты-значения – полезный концепт проблемного-ориентированного программирования. При переопределении метода equals вы можете воспользоваться преимуществами этой особенности в C#, чтобы получить доступ к скрытым переменным однотипных классов.
public class Example
{
private string _someValue;
public void DoSomething(Example otherObject)
{
_someValue = otherObject._someValue; // Это еще что такое? Но ведь доступ к скрытой переменной нельзя получить из другого объекта!
}
}
Но, как ни странно, он работает.
Оказывается, к переменной Private можно получить доступ из другого объекта того же типа. И, если задуматься, это вполне логично. Зачем нужны переменные Private? Для инкапсуляции и управления состоянием объекта.
Представьте, что нам нужно создать контейнер с ограниченной емкостью – коробку конфет. Мы могли бы сделать это так:
public class Chocolate
{
public string Name { get; set; }
}
public class ChocolateBox
{
public List<Chocolate> Chocolates { get; set; }
public bool IsValid()
{
return Chocolates.Count > -1 && Chocolates.Count < 11;
}
}
Догадайтесь, в чем проблема
Несмотря на то, что у нас есть метод IsValid, в коробку можно добавить больше, чем максимальное количество конфет (хотя, думаю, заказчик был бы не против). Чтобы решить эту проблему, мы можем инкапсулировать коробку и добавить методы для соблюдения необходимых ограничений. К тому же это позволит нам избавиться от метода IsValid.
public class Chocolate
{
public string Name { get; set; }
}
public class ChocolateBox
{
private List<Chocolate> _chocolates;
public void AddChocolate(Chocolate chocolate)
{
if(_chocolates == null) _chocolates = new List<Chocolate>();
if(_chocolates.Count > 10) throw new Exception("No more space");
_chocolates.Add(chocolate);
}
// Other methods removed for brevity
}
Но здесь есть одно условие.
Разработчики, создающие другие части системы, могут не догадываться, как работает ваша «коробка». В таком случае на помощь приходит модульное или интеграционное тестирование. Чтобы избежать любых недоразумений с кодом, нужно просто сделать неявное явным.
Но если разработчик знаком с классом, ему можно доверить изменение внутреннего состояния – в том числе для других классов того же типа.
Уверен, что из этой особенности можно извлечь массу плюсов, но хочу выделить лишь один, который оказался для меня особенно полезным.
Доступ к скрытым переменным при создании объектов-значений
Объекты-значения – это один из шаблонов проблемно-ориентированного программирования, который также можно использовать за пределами этой модели. Отличительная черта таких объектов в том, что их равенство основывается не на идентификаторе, а на значении.
Простой пример – строка. Две строки с одними и теми же символами в одинаковом порядке считаются равнозначными. Аналогично можно сравнивать телефонные номера из системы CRM, если, конечно, вы не пишете ПО для оператора телефонной связи – в таком случае телефонный номер может выступать идентификатором клиента и, следовательно, не быть объектом-значением.
В большинстве случаев объекты-значения неизменяемы. В том числе это касается строк: «изменяя» строку, вы, по сути, создаете новый объект. Чем полезна такая неизменяемость? Мне приходит на ум 4 причины:
• Объекты намного легче тестировать.
• Объекты легче распараллелить.
• Можно быть уверенным, что они всегда в действительном состоянии.
• Не нужно отслеживать много идентификаторов в приложении (пожалуй, основная причина).
Пример объекта-значения
В качестве примера давайте смоделируем контейнер для жидкости или, проще говоря, чашку с максимальной емкостью в 1 литр. При этом мы можем перелить содержимое из одной чашки в другую (с точки зрения кода для этого понадобится еще один экземпляр чашки). Жидкость, превышающая максимальный объем, считается утерянной. Если же две чашки содержат одинаковый объём жидкости, они равны.
Начнем с тестирования. Чтобы чашка была неизменяемой, передадим текущий объем жидкости в конструктор. Для начала проверим, можем ли мы создать чашку, наполненную жидкостью.
[Test]
public void Constructor_ValidVolume_ObjectIsNotNull()
{
var cup = new Cup(0.5m);
Assert.NotNull(cup);
}
Теперь удостоверимся, что чашка не может содержать недопустимый объем – более одного или менее нуля литров.
[TestCase(-0.1)]
[TestCase(1.1)]
public void Constructor_InvalidVolumes_ThrowsException(decimal invalidVolume)
{
ArgumentOutOfRangeException expected = new ArgumentOutOfRangeException("volume");
ArgumentOutOfRangeException actual = null;
try
{
var cup = new Cup(invalidVolume);
}
catch (ArgumentOutOfRangeException ex)
{
actual = ex;
}
Assert.AreEqual(expected.ParamName, actual.ParamName);
}
А теперь самое интересное. Как сделать, чтобы чашки были одинаковыми? Для этого нам нужен доступ к скрытым переменным.
[Test]
public void Equals_TwoEqualCups_AreEqual()
{
var cup1 = new Cup(0.5m);
var cup2 = new Cup(0.5m);
Assert.AreEqual(cup1, cup2);
}
На досуге можете потренироваться с переливанием жидкости. Впрочем, возникает интересный вопрос: как перелить жидкость из чашки, если она неизменяема? :)
Вывод
Объекты-значения – полезный концепт проблемного-ориентированного программирования. При переопределении метода equals вы можете воспользоваться преимуществами этой особенности в C#, чтобы получить доступ к скрытым переменным однотипных классов.
Поделиться с друзьями
lair
А где в этом коде мы покинули пределы класса-то?
Ну и в чем "разоблачение", спрашивается?
PS Отдельный зачет, конечно, в том, что тесты есть, а реализации — нет. Ну и как же "скрытые поля" помогают нам создавать объекты-значения?