#c#
#c#
Вопрос:
Постановка проблемы
Существует пользовательский векторный класс:
namespace StackoverflowQuestion1
{
public class MyVector
{
public float x;
public float y;
public float z;
public MyVector(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
}
}
Существует интерфейс для всего, что является подвижным, что означает, что позиции могут меняться:
namespace StackoverflowQuestion1
{
public interface IMovable
{
public string Name { get; }
public MyVector Position { get; }
}
}
Мебель подвижна, поэтому она реализует соответствующий интерфейс:
namespace StackoverflowQuestion1
{
public class Furniture : IMovable
{
public string Name { get; private set; }
public MyVector Position { get; private set; }
public Furniture(string name, float x, float y, float z)
{
this.Name = name;
this.Position = new MyVector(x, y, z);
}
}
}
Как и ожидалось, доступ к частному получателю имени невозможен. Доступ к частному установщику позиции также не работает, как и ожидалось. Однако доступ к полям позиции возможен, поскольку они являются общедоступными.
using StackoverflowQuestion1;
class Program
{
static void Main(string[] args)
{
Furniture F = new Furniture("Chair", 1f, 2f, 3f);
F.Name = "Office chair"; // doesn't work, as expected
F.Position = new MyVector(5f, 6f, 7f); // doesn't work, as expected
F.Position.x = 5f; // works, unfortunately
F.Position.y = 6f; // works, unfortunately
F.Position.z = 7f; // works, unfortunately
}
}
Вопрос
Как сделать невозможным изменение положения мебели, не делая координаты MyVector
частными и, следовательно, недоступными? Я хочу иметь инкапсуляцию, позволяя только Furniture
членам получать доступ к позиции, но MyVector
станет бесполезным в других местах, если его значения нельзя изменить.
Комментарии:
1. Рассматривали ли вы возможность создания
MyVector
структуры (или использования существующей векторной структуры)?2. Вы можете создать пользовательский геттер для
Position
, вFurniture
котором возвращается копияMyVector
объекта, поэтому любые изменения, внесенные в эту копию, не вносятся в оригинал. Но если ваше требование заключается в том, что поляMyVector
должны быть общедоступными, тогда они являются общедоступными и, следовательно, доступными.3. Сделайте вектор неизменяемой структурой (или структурой записи) и используйте выражение with (ссылка на C #) , чтобы получить измененную копию.
Ответ №1:
Здесь нужно сделать несколько замечаний:
- По замыслу вы решили создать поля
public
, что означает, что они легко доступны из других классов. Они не являются частными, что и следует из названия. Чтобы заставить их быть доступными только для чтения, используйтеreadonly
ключевое словоpublic class MyVector { public readonly float x; public readonly float y; public readonly float z; public MyVector(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } }
- Обычно вы не предоставляете поля, а вместо этого используете свойства только с определенными геттерами.
public class MyVector { private readonly float x; private readonly float y; private readonly float z; public MyVector(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } public float X { get => x; } public float Y { get => y; } public float Z { get => z; } }
- Кроме того, вы можете упростить задачу, используя автоматические свойства
public class MyVector { public MyVector(float x, float y, float z) { this.X = x; this.Y = y; this.Z = z; } public float X { get; } public float Y { get; } public float Z { get; } }
- Наконец, это рекомендуется для семантики значений, где (x, y, z) всегда будут идти вместе, чтобы использовать
struct
объявления.public readonly struct MyVector { public MyVector(float x, float y, float z) { this.X = x; this.Y = y; this.Z = z; } public float X { get; } public float Y { get; } public float Z { get; } }
В качестве примечания, если вы попытаетесь изменить содержимое структуры, предоставляемой свойством, C # будет жаловаться.
Рассмотрим этот код
public struct MyVector
{
public float x;
public float y;
public float z;
public MyVector(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
}
public class Movable
{
public Movable(MyVector position)
{
Position = position;
}
public MyVector Position { get; }
}
Таким образом, даже если дизайн позволяет изменять содержимое MyVector
(изменять), компилятор остановит вас. Это связано с тем, что с struct
типами у вас везде есть локальные копии данных, и при записи Position.x = 10f
вы бы изменили локальную копию Position
, которая существует в области, где это вызывается, а не изменили исходные данные.
В вопросе MyVector
указан класс, который, таким Position.x = 10f
образом, изменяет исходные данные, и, как указано, это нежелательное поведение, поэтому выполните описанные выше действия, чтобы запретить это поведение.
Чтобы обеспечить MyVector
хорошую работу с другими классами, я часто добавляю следующие функциональные возможности к таким дефляциям. Я добавляю поддержку .ToString()
с форматированием и добавляю поддержку .Equals()
(и ==
для структур), чтобы можно было писать такой код:
static void Main(string[] args)
{
var pos = new MyVector(1f, 1/2f, 1/3f);
var m = new Movable(pos);
if (m.Position == pos)
{
Console.WriteLine($"{m.Position:f2}");
// (1.00,0.50,0.33)
}
}
Обратите внимание на форматирование с 2 десятичными знаками и проверку равенства.
вот полный код, который позволяет это для вашей справки
MyVector.cs
public readonly struct MyVector : IEquatable<MyVector>, IFormattable
{
public MyVector(float x, float y, float z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
public float X { get; }
public float Y { get; }
public float Z { get; }
#region IEquatable Members
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(MyVector)</code></returns>
public override bool Equals(object obj)
{
if (obj is MyVector other)
{
return Equals(other);
}
return false;
}
public static bool operator ==(MyVector target, MyVector other) { return target.Equals(other); }
public static bool operator !=(MyVector target, MyVector other) { return !(target == other); }
/// <summary>
/// Checks for equality among <see cref="MyVector"/> classes
/// </summary>
/// <param name="other">The other <see cref="MyVector"/> to compare it to</param>
/// <returns>True if equal</returns>
public bool Equals(MyVector other)
{
return X.Equals(other.X)
amp;amp; Y.Equals(other.Y)
amp;amp; Z.Equals(other.Z);
}
/// <summary>
/// Calculates the hash code for the <see cref="MyVector"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode()
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295) * hc X.GetHashCode();
hc = (-1521134295) * hc Y.GetHashCode();
hc = (-1521134295) * hc Z.GetHashCode();
return hc;
}
}
#endregion
#region Formatting
public override string ToString() => ToString("g");
public string ToString(string formatting) => ToString(formatting, null);
public string ToString(string format, IFormatProvider provider)
{
return $"({X.ToString(format, provider)},{Y.ToString(format, provider)},{Z.ToString(format, provider)})";
}
#endregion
}
Комментарии:
1. См. Также с выражением (ссылка на C #) .
Ответ №2:
Проблема с использованием частного установщика для объекта заключается в том, что он только мешает вам полностью заменить объект. Поскольку это не неизменяемый объект, вы все равно можете получить доступ к его свойствам, изменив их, как вы обнаружили.
Вы могли бы определить IMyVector
интерфейс с получением только свойств, MyVector
реализовать его, а затем использовать интерфейс для вашего общедоступного Position
свойства.
public interface IMyVector
{
float x {get;}
...
}
public class MyVector : IMyVector
{
...
}
public class Furniture : IMovable
{
public string Name { get; private set; }
public IMyVector Position { get; private set; }
...
Ответ №3:
Другая возможность проектирования — объявлять IMyReadOnlyVector
интерфейс и предоставлять его всякий раз, когда мы не хотим разрешать векторы изменений:
public interface IMyReadOnlyVector {
float x { get; }
float y { get; }
float z { get; }
}
public interface IMyVector : IMyReadOnlyVector {
float x { get; set; }
float y { get; set; }
float z { get; set; }
}
Затем вы реализуете MyVector
:
public class MyVector : IMyVector {
public MyVector(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
public float x { get; set; }
public float y { get; set; }
public float z { get; set; }
}
Теперь время для хитрости: IMovable
использует IMyReadOnlyVector
интерфейс: мы позволяем пользователю видеть Position
, но не разрешаем его изменять.
public interface IMovable {
string Name { get; }
// User can see position, but not allowed to change it
IMyReadOnlyVector Position { get; }
}
public class Furniture : IMovable {
// Private usage only: we don't want user explicitly change position
private MyVector m_Position;
public string Name { get; private set; }
// Public usage: user can't change vector's coordinates here
public IMyReadOnlyVector Position => m_Position;
public Furniture(string name, float x, float y, float z) {
this.Name = name;
this.m_Position = new MyVector(x, y, z);
}
// But we can change Position within the class
public void ShiftMe(int dx, int dy, int dz) {
m_Position.x = dx;
m_Position.y = dy;
m_Position.z = dz;
}
}