Что такое & ldquo; в основном полная & rdquo; (im) изменчивость подход для C #? [закрыто]

Попробуйте следующее:

Определение таблицы:

DROP TABLE IF EXISTS category;
CREATE TABLE category (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(20),
    parent_id INT,
    CONSTRAINT fk_category_parent FOREIGN KEY (parent_id)
    REFERENCES category (id)
) engine=innodb;

Экспериментальные строки:

INSERT INTO category VALUES
(19, 'category1', NULL),
(20, 'category2', 19),
(21, 'category3', 20),
(22, 'category4', 21),
(23, 'categoryA', 19),
(24, 'categoryB', 23),
(25, 'categoryC', 23),
(26, 'categoryD', 24);

Рекурсивная хранимая процедура:

DROP PROCEDURE IF EXISTS getpath;
DELIMITER $$
CREATE PROCEDURE getpath(IN cat_id INT, OUT path TEXT)
BEGIN
    DECLARE catname VARCHAR(20);
    DECLARE temppath TEXT;
    DECLARE tempparent INT;
    SET max_sp_recursion_depth = 255;
    SELECT name, parent_id FROM category WHERE id=cat_id INTO catname, tempparent;
    IF tempparent IS NULL
    THEN
        SET path = catname;
    ELSE
        CALL getpath(tempparent, temppath);
        SET path = CONCAT(temppath, '/', catname);
    END IF;
END$$
DELIMITER ;

Функция обертки для хранимой процедуры:

DROP FUNCTION IF EXISTS getpath;
DELIMITER $$
CREATE FUNCTION getpath(cat_id INT) RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE res TEXT;
    CALL getpath(cat_id, res);
    RETURN res;
END$$
DELIMITER ;

Выберите пример:

SELECT id, name, getpath(id) AS path FROM category;

Выход:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 19 | category1 | category1                               |
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
| 23 | categoryA | category1/categoryA                     |
| 24 | categoryB | category1/categoryA/categoryB           |
| 25 | categoryC | category1/categoryA/categoryC           |
| 26 | categoryD | category1/categoryA/categoryB/categoryD |
+----+-----------+-----------------------------------------+

Фильтрация строк определенным путем :

SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';

Выход:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+
30
задан Community 23 May 2017 в 12:17
поделиться

3 ответа

Лично я не знаю каких-либо сторонних или предыдущих решений этой проблемы, поэтому приношу свои извинения, если я покрываю старые позиции. Но если бы я собирался реализовать какой-то стандарт неизменности для проекта, над которым я работал, я бы начал с чего-то вроде этого:

public interface ISnaphot<T>
{
    T TakeSnapshot();
}

public class Immutable<T> where T : ISnaphot<T>
{
    private readonly T _item;
    public T Copy { get { return _item.TakeSnapshot(); } }

    public Immutable(T item)
    {
        _item = item.TakeSnapshot();
    }
}

Этот интерфейс был бы реализован примерно так:

public class Customer : ISnaphot<Customer>
{
    public string Name { get; set; }
    private List<string> _creditCardNumbers = new List<string>();
    public List<string> CreditCardNumbers { get { return _creditCardNumbers; } set { _creditCardNumbers = value; } }

    public Customer TakeSnapshot()
    {
        return new Customer() { Name = this.Name, CreditCardNumbers = new List<string>(this.CreditCardNumbers) };
    }
}

И клиентский код будет выглядеть примерно так:

    public void Example()
    {
        var myCustomer = new Customer() { Name = "Erik";}
        var myImmutableCustomer = new Immutable<Customer>(myCustomer);
        myCustomer.Name = null;
        myCustomer.CreditCardNumbers = null;

        //These guys do not throw exceptions
        Console.WriteLine(myImmutableCustomer.Copy.Name.Length);
        Console.WriteLine("Credit card count: " + myImmutableCustomer.Copy.CreditCardNumbers.Count);
    }

Очевидный недостаток заключается в том, что реализация хороша только как клиент реализации ISnapshot TakeSnapshot, но на по крайней мере, это будет стандартизировать вещи, и вы будете знать, где искать, если у вас есть проблемы, связанные с сомнительной изменчивостью. Бремя также будет лежать на потенциальных разработчиках, чтобы понять, могут ли они обеспечить неизменность снимка и не реализовать интерфейс, если нет (то есть класс возвращает ссылку на поле, которое не поддерживает какой-либо вид клонирования / копирования и, следовательно, не может быть снимок-е изд.)

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

Я понимаю, что это не так уж далеко от простой реализации объектной копии, но она стандартизирует копию с точки зрения неизменности. В кодовой базе вы можете увидеть некоторых разработчиков из ICloneable, некоторых конструкторов копирования и некоторые явные методы копирования, возможно, даже в одном классе. Определив что-то вроде этого, вы узнаете, что намерение определенно связано с неизменяемостью - я хочу сделать снимок, а не дублирующий объект, потому что мне нужно больше и этого объекта. Класс Immtuable<T> также централизует связь между неизменяемостью и копиями; если позже вы захотите как-то оптимизировать, например, кэшировать снимок до «грязного», вам не нужно делать это во всех разработчиках логики копирования.

2
ответ дан CRAGIN 23 May 2017 в 12:17
поделиться

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

Нет необходимости использовать дженерики или использовать какие-либо интерфейсы. Для моих целей я не хочу, чтобы мои неизменяемые классы были конвертируемыми друг в друга. Почему ты? Какие общие черты они должны разделить, что означает, что они должны быть обратимы друг к другу? Применение шаблона кода должно быть задачей генератора кода (или, что еще лучше, достаточно удобной системы типов, позволяющей вам определять общие шаблоны кода, которых, к сожалению, в C # нет).

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

public sealed partial class CommitPartial
{
    public CommitID ID { get; private set; }
    public TreeID TreeID { get; private set; }
    public string Committer { get; private set; }
    public DateTimeOffset DateCommitted { get; private set; }
    public string Message { get; private set; }

    public CommitPartial(Builder b)
    {
        this.ID = b.ID;
        this.TreeID = b.TreeID;
        this.Committer = b.Committer;
        this.DateCommitted = b.DateCommitted;
        this.Message = b.Message;
    }

    public sealed class Builder
    {
        public CommitID ID { get; set; }
        public TreeID TreeID { get; set; }
        public string Committer { get; set; }
        public DateTimeOffset DateCommitted { get; set; }
        public string Message { get; set; }

        public Builder() { }

        public Builder(CommitPartial imm)
        {
            this.ID = imm.ID;
            this.TreeID = imm.TreeID;
            this.Committer = imm.Committer;
            this.DateCommitted = imm.DateCommitted;
            this.Message = imm.Message;
        }

        public Builder(
            CommitID pID
           ,TreeID pTreeID
           ,string pCommitter
           ,DateTimeOffset pDateCommitted
           ,string pMessage
        )
        {
            this.ID = pID;
            this.TreeID = pTreeID;
            this.Committer = pCommitter;
            this.DateCommitted = pDateCommitted;
            this.Message = pMessage;
        }
    }

    public static implicit operator CommitPartial(Builder b)
    {
        return new CommitPartial(b);
    }
}

Базовая схема состоит в том, чтобы иметь неизменный класс с вложенным изменяемым Builder класс, который используется для создания экземпляров неизменяемого класса изменяемым образом. Единственный способ установить свойства неизменяемого класса - это создать класс ImmutableType.Builder и установить его обычным изменяемым способом и преобразовать его в содержащий его класс ImmutableType с помощью оператора неявного преобразования.

Вы можете расширить шаблон T4, добавив общедоступный ctor по умолчанию к самому классу ImmutableType, чтобы избежать двойного распределения, если вы можете установить все свойства заранее.

Вот пример использования:

CommitPartial cp = new CommitPartial.Builder() { Message = "Hello", OtherFields = value, ... };

или ...

CommitPartial.Builder cpb = new CommitPartial.Builder();
cpb.Message = "Hello";
...
// using the implicit conversion operator:
CommitPartial cp = cpb;
// alternatively, using an explicit cast to invoke the conversion operator:
CommitPartial cp = (CommitPartial)cpb;

Обратите внимание, что в присваивании используется оператор неявного преобразования из CommitPartial.Builder в CommitPartial. Это та часть, которая «замораживает» изменяемый CommitPartial.Builder путем создания из него нового неизменяемого экземпляра CommitPartial с нормальной семантикой копирования.

3
ответ дан James Dunne 23 May 2017 в 12:17
поделиться

Лучшим решением было бы просто использовать F # там, где вы хотите истинную неизменность?

5
ответ дан Chris Marisic 23 May 2017 в 12:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: