Любое преимущество для объектов. GetObject (i) перед объектами [i]? - PullRequest
6 голосов
/ 01 июля 2010

Я немного рефакторинг кода доступа к данным C # от предыдущего разработчика, и мне любопытно, какой шаблон он использовал.

Код, изначально предоставляющий коллекции (массивы) различных бизнес-объектов в стиле ActiveRecord - по существу, объекты, обертывающие поля базы данных. Я изменяю массивы на общие списки, но аспект кода, который мне интересен, заключается в том, что предыдущий разработчик имел методы Get для каждого типа объектов, которые он упаковывал, поэтому:

public Thing GetThing(int i) {
    return things[i];
}

Существует несколько таких методов, и я не могу представить себе возможного преимущества использования этого механизма по сравнению с простой ссылкой на вещи [i] напрямую. Предположим, ради аргумента, что вещи являются публичным свойством, а не публичным полем (в данном случае это фактически автоматически реализуемое свойство, так что предположение на самом деле верно).

Я что-то упускаю из виду? Или даже что-то эзотерическое?

UPDATE Я, вероятно, должен уточнить, что эти коллекции в настоящее время доступны из цикла for:

for (int i = 0; i < thingsCount; i== ) {
    dosomthing( GetThing(i) );
    dosomethingelse( GetThing(i) );
}

который я рефакторинг:

for (int i = 0; i < thingsCount; i== ) {
    Thing thing = things[i];
    dosomthing( thing );
    dosomethingelse( thing );
}

и, возможно, даже использовать вещи .foreach ().

Ответы [ 5 ]

6 голосов
/ 01 июля 2010

Не знаю, очевидно ли это, но я думаю, что вы что-то упустили.

Скажем, things - это IList<Thing>. Тогда его непосредственное раскрытие (как Things) позволит вызывающему коду вызвать Add, Insert, RemoveAt и т. Д. Возможно, предыдущий разработчик не хотел этого допускать (и я уверен, что их достаточно веских причин для этого).

Даже если предположить, что это Thing[] (так что Add и т. Д. Не будут доступны), выставление его как такового все равно позволит вызывающему коду выполнить что-то вроде obj.Things[0] = new Thing();, что может быть операцией, которая не должна быть разрешено в зависимости от реализации класса.

Вы могли бы выставить Things как ReadOnlyCollection<Thing>, который позаботился бы о большинстве этих проблем. Но это сводится к следующему: если разработчик only хочет разрешить вызывающему коду доступ к элементам по индексу - не более того, - в качестве средства для этого используется единственный метод GetThing, Честно говоря, имеет гораздо больше смысла.

Теперь, конечно, есть и такая опция: реализация свойства this[int] только с аксессором get. Но это имеет смысл только в том случае, если рассматриваемый класс по сути является коллекцией исключительно Thing объектов (т. Е. Не существует также коллекции некоторого типа другого объекта, который вы хотите обеспечить доступ к классу).

В общем и целом, я думаю, что подход GetThing довольно здоровый.

Тем не менее, из-за того, как вы сформулировали свой вопрос, похоже, что предыдущий разработчик принял некоторые другие довольно плохие решения:

  1. Если он / она также выставил коллекцию things непосредственно как общественное достояние, ну, тогда ... это наносит ущерб всей цели метода GetThing, не так ли? В результате получается просто раздутый интерфейс (я вообще думаю, что это не очень хороший знак, когда у вас есть несколько методов для выполнения одной и той же вещи, если они не задокументированы как псевдонимы по какой-то оправданной причине). [ Обновление : Похоже, предыдущий разработчик не сделал это. Хорошо.]
  2. Похоже, предыдущий разработчик также внутренне обращался к элементам из things, используя метод GetThing, что просто глупо (на мой взгляд). Зачем вводить бессмысленные накладные расходы на дополнительные вызовы методов, используя открытый интерфейс класса внутри самого класса? Если вы в классе, вы уже в реализации и можете получить доступ к закрытым / защищенным данным, как вам угодно - не нужно притворяться иначе.
2 голосов
/ 01 июля 2010

Вы, вероятно, хотите выставить объект как IList<Thing>. Это даст вам возможности индексирования, которые вы ищете, но вы также сможете использовать ряд функций LINQ, таких как создание нового списка элементов на основе условий. Плюс IList<T> реализует IEnumerable<T>, поэтому вы сможете использовать foreach для циклического перемещения по объектам.

например. в вашем классе:

public IList<Thing> Things { get; private set; }

например. Использование:

Thing x = business.Things[3];

или

var x = business.Things.Where(t => t.Name == "cori");
2 голосов
/ 01 июля 2010

Здесь нужно отметить две вещи. Во-первых, вы хотите, чтобы переменные объекта были приватными, а для доступа к ним используйте методы получения и установки. Это предотвращает случайное изменение или изменение переменных объекта пользователем.

Во-вторых, считается хорошим соглашением об именах, когда термины get / set должны использоваться, когда к атрибуту обращаются напрямую. Это помогает улучшить читаемость.

Хотя в этом случае это является общедоступным свойством, использование методов получения и установки улучшает читабельность и помогает предотвратить непредвиденное поведение. Не имеет значения, обращаетесь ли вы к объектным переменным в цикле, вы должны продолжать использовать соглашение GetThing.

Наконец, если вы обращаетесь к переменным внутри объекта, вам не нужно использовать геттер или сеттер.

ПРИМЕЧАНИЕ. Обычно считается хорошим стилем сохранять переменные объекта частными и использовать методы получения / установки для всех языков

Рекомендации по стилю C ++

Руководство по стилю C #

Вас также может заинтересовать вопрос " getter and setter для класса в классе c # "

1 голос
/ 01 июля 2010

Если бы мне пришлось угадывать, я бы сказал, что этот разработчик привык к другому языку, такому как Java, и не был полностью осведомлен о стандартной практике в C #.Номенклатура «get [Property]» очень интенсивно используется в Java, javascript и т. Д. C # заменяет это свойствами и индексаторами.Свойства столь же мощны, как и методы получения и установки, но их легче писать и использовать.Единственный раз, когда вы обычно видите «Получить [что-то]» в C #, это если:

  • Операция, вероятно, будет достаточно дорогой, так что вы действительно хотите отвезти домой тот факт, что это не простой доступ к элементу(например, GetPrimeNumbers()) или
  • Ваша коллекция фактически содержит несколько проиндексированных коллекций.(Например, GetRow(int i) и GetColumn(int i)). Даже в этом случае чаще всего просто представлять каждую из этих проиндексированных коллекций как свойство для себя, которое имеет индексированный тип ("table.Rows[2]").

Если вы только обращаетесь к этим значениям в циклах for, коллекция должна реализовать IEnumerable<Thing>, что даст вам доступ к методам LINQ и конструкции foreach.вам нужно иметь индексированные геттеры, вам следует рассмотреть возможность использования собственного интерфейса, который расширяет IEnumerable<T>, но дополнительно обеспечивает:

T this[int i] { get; }

Таким образом, у потребителей не создается впечатление, что они могут Add и Remove объекты в этой коллекции.

Обновление

Я знаю, что это в основном вопрос стиля, который подлежит обсуждению, но я действительно думаю, чтоРешение GetThings не является правильным способом решения проблемы. Следующая стратегия, хотя она и требует немного больше работы, в большей степени соответствует способу разработки стандартных классов и сред .NET:

public class ThingHolderDataAccess
{
    public ThingHolder GetThingHolderForSomeArgs(int arg1, int arg2)
    {
        var oneThings = GetOneThings(arg1);
        var otherThings = GetOtherThings(arg2);
        return new ThingHolder(oneThings, otherThings);
    }
    private IEnumerable<OneThing> GetOneThings(int arg)
    {
        //...
        return new List<OneThing>();
    }
    private IEnumerable<AnotherThing> GetOtherThings(int arg2)
    {
        //...
        return new List<AnotherThing>();
    }
}

public class ThingHolder
{
    public IIndexedReadonlyCollection<OneThing> OneThings
    {
        get;
        private set;
    }

    public IIndexedReadonlyCollection<AnotherThing> OtherThings
    {
        get;
        private set;
    }

    public ThingHolder(IEnumerable<OneThing> oneThings,
                       IEnumerable<AnotherThing> otherThings)
    {
        OneThings = oneThings.ToIndexedReadOnlyCollection();
        OtherThings = otherThings.ToIndexedReadOnlyCollection();
    }
}

#region These classes can be written once, and used everywhere
public class IndexedCollection<T> 
    : List<T>, IIndexedReadonlyCollection<T>
{
    public IndexedCollection(IEnumerable<T> items)
        : base(items)
    {
    }
}

public static class EnumerableExtensions
{
    public static IIndexedReadonlyCollection<T> ToIndexedReadOnlyCollection<T>(
        this IEnumerable<T> items)
    {
        return new IndexedCollection<T>(items);
    }
}

public interface IIndexedReadonlyCollection<out T> : IEnumerable<T>
{
    T this[int i] { get; }
}
#endregion

Использование приведенного выше кода может выглядеть примерно так:

var things = _thingHolderDataAccess.GetThingHolderForSomeArgs(a, b);
foreach (var oneThing in things.OneThings)
{
    // do something
}
foreach (var anotherThing in things.OtherThings)
{
    // do something else
}

var specialThing = things.OneThings[c];
// do something to special thing
0 голосов
/ 01 июля 2010

Как и в других ответах, это проблема ограничения доступа к самому массиву (или списку).

Как правило, вы не хотите, чтобы клиенты класса могли напрямую изменять сам массив. Сокрытие реализации базового списка также позволяет изменить реализацию в будущем, не затрагивая любой клиентский код, который использует класс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...