Чистый код: читабельные предложения по внедрению зависимостей?

У меня есть проект, который добавляет элементы в чертеж AutoCad. Я заметил, что начинаю писать одни и те же десять строк кода в нескольких методах (показаны только два для простоты).

Первоначальная реализация: вы заметите, что единственное, что действительно меняется, — это добавление линии вместо окружности.

[CommandMethod("Test", CommandFlags.Session)]
    public void Test()
    {
        AddLineToDrawing();
        AddCircleToDrawing();
    }

    private void AddLineToDrawing()
    {
        using (DocumentLock lockedDocument = Application.DocumentManager.MdiActiveDocument.LockDocument())
        {
            using (Database database = Application.DocumentManager.MdiActiveDocument.Database)
            {
                using (Transaction transaction = database.TransactionManager.StartTransaction())//Start the transaction
                {
                    //Open the block table for read
                    BlockTable blockTable = transaction.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable;

                    //Open the block table record model space for write
                    BlockTableRecord blockTableRecord = (BlockTableRecord)transaction.GetObject(blockTable[BlockTableRecord.ModelSpace], OpenMode.ForWrite);

                    Line line = new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0));
                    blockTableRecord.AppendEntity(line);

                    transaction.AddNewlyCreatedDBObject(line, true);

                    transaction.Commit();
                }
            }
        }
    }

    private void AddCircleToDrawing()
    {
        using (DocumentLock lockedDocument = Application.DocumentManager.MdiActiveDocument.LockDocument())
        {
            using (Database database = Application.DocumentManager.MdiActiveDocument.Database)
            {
                using (Transaction transaction = database.TransactionManager.StartTransaction())//Start the transaction
                {
                    //Open the block table for read
                    BlockTable blockTable = transaction.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable;

                    //Open the block table record model space for write
                    BlockTableRecord blockTableRecord = (BlockTableRecord)transaction.GetObject(blockTable[BlockTableRecord.ModelSpace], OpenMode.ForWrite);

                    Circle circle = new Circle(new Point3d(0, 0, 0), new Vector3d(0, 0, 0), 10);
                    blockTableRecord.AppendEntity(circle);

                    transaction.AddNewlyCreatedDBObject(circle, true);

                    transaction.Commit();
                }
            }
        }
    }

Внедрение: этот подход устранил дублирование кода, но я думаю, что читабельность плохая.

[CommandMethod("Test", CommandFlags.Session)]
    public void Test()
    {
        PerformActionOnBlockTable(new CircleDrawer());
        PerformActionOnBlockTable(new LineDrawer());
    }

    public interface IDraw
    {
        DBObject DrawObject(BlockTableRecord blockTableRecord);
    }

    public class CircleDrawer : IDraw
    {
        public DBObject DrawObject(BlockTableRecord blockTableRecord)
        {
            Circle circle = new Circle(new Point3d(0, 0, 0), new Vector3d(0, 0, 0), 10);
            blockTableRecord.AppendEntity(circle);

            return circle;
        }
    }

    public class LineDrawer : IDraw
    {
        public DBObject DrawObject(BlockTableRecord blockTableRecord)
        {
            Line line = new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0));
            blockTableRecord.AppendEntity(line);

            return line;
        }
    }

    private void PerformActionOnBlockTable(IDraw drawer)
    {
        using (DocumentLock lockedDocument = Application.DocumentManager.MdiActiveDocument.LockDocument())
        {
            using (Database database = Application.DocumentManager.MdiActiveDocument.Database)
            {
                using (Transaction transaction = database.TransactionManager.StartTransaction())//Start the transaction
                {
                    //Open the block table for read
                    BlockTable blockTable = transaction.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable;

                    //Open the block table record model space for write
                    BlockTableRecord blockTableRecord = (BlockTableRecord)transaction.GetObject(blockTable[BlockTableRecord.ModelSpace], OpenMode.ForWrite);

                    DBObject newObject = drawer.DrawObject(blockTableRecord);

                    transaction.AddNewlyCreatedDBObject(newObject, true);

                    transaction.Commit();
                }
            }
        }
    }

Injecting Func‹>: Это, кажется, дало мне похожий результат, но с лучшей читабельностью.

[CommandMethod("Test", CommandFlags.Session)]
    public void Test()
    {
        PerformActionOnBlockTable(AddLineToDrawing);
        PerformActionOnBlockTable(AddCircleToDrawing);
    }

    private void PerformActionOnBlockTable(Func<BlockTableRecord, DBObject> action)
    {
        using (DocumentLock lockedDocument = Application.DocumentManager.MdiActiveDocument.LockDocument())
        {
            using (Database database = Application.DocumentManager.MdiActiveDocument.Database)
            {
                using (Transaction transaction = database.TransactionManager.StartTransaction())//Start the transaction
                {
                    //Open the block table for read
                    BlockTable blockTable = transaction.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable;

                    //Open the block table record model space for write
                    BlockTableRecord blockTableRecord = (BlockTableRecord)transaction.GetObject(blockTable[BlockTableRecord.ModelSpace], OpenMode.ForWrite);

                    DBObject newObject = action(blockTableRecord);

                    transaction.AddNewlyCreatedDBObject(newObject, true);

                    transaction.Commit();
                }
            }
        }
    }

    private DBObject AddLineToDrawing(BlockTableRecord blockTableRecord)
    {
        Line line = new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0));
        blockTableRecord.AppendEntity(line);

        return line;
    }

    private DBObject AddCircleToDrawing(BlockTableRecord blockTableRecord)
    {
        Circle circle = new Circle(new Point3d(0, 0, 0), new Vector3d(0, 0, 0), 10);
        blockTableRecord.AppendEntity(circle);

        return circle;
    }

Я могу честно сказать, что я мало что сделал с DI, так что я совсем новичок в этом. Может ли кто-нибудь из вас, более опытных разработчиков, дать мне плюсы и минусы двух разных подходов? Есть ли что-нибудь в последнем подходе, что является красным флагом? Это кажется более читаемым, чем второй подход. Может быть, я даже не совсем понимаю инъекцию... Заранее спасибо за ваш вклад!


person JSprang    schedule 29.09.2010    source источник


Ответы (2)


Вы можете выполнить простой рефакторинг вместо предоставленных вами опций:

[CommandMethod("Test", CommandFlags.Session)]   
public void Test() {   
  AddLineToDrawing();   
  AddCircleToDrawing();   
}  

private void AddLineToDrawing() {   
  CreateObjectOnBlockTable(
    new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0)));   
}   

private void AddCircleToDrawing() {   
  CreateObjectOnBlockTable(
    new Circle(new Point3d(0, 0, 0), new Vector3d(0, 0, 0), 10));   
}   

private void CreateObjectOnBlockTable(DBObject dbObject) { 
  using (var lockedDocument = Application.DocumentManager.MdiActiveDocument.LockDocument()) 
  using (var database = Application.DocumentManager.MdiActiveDocument.Database) 
  using (var transaction = database.TransactionManager.StartTransaction()) {
    // Open the block table for read 
    var blockTable = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead); 

    // Open the block table record model space for write 
    var blockTableRecord = (BlockTableRecord)transaction.GetObject(blockTable[BlockTableRecord.ModelSpace], OpenMode.ForWrite); 

    blockTableRecord.AppendEntity(dbObject); 
    transaction.AddNewlyCreatedDBObject(dbObject, true); 
    transaction.Commit(); 
  } 
} 

Я думаю, что это более читабельно.

ОБНОВЛЕНИЕ: для запуска специальной логики мне нравится идея использования делегатов. Я бы реорганизовал код следующим образом:

private void CreateObjectOnBlockTable(DBObject dbObject) {
  PerformActionOnBlockTable((transaction, blockTableRecord) => {
    blockTableRecord.AppendEntity(dbObject);  
    transaction.AddNewlyCreatedDBObject(dbObject, true);    
  });
}

private void PerformActionOnBlockTable(Action<Transaction, BlockTableRecord> action) {  
  using (var lockedDocument = Application.DocumentManager.MdiActiveDocument.LockDocument())  
  using (var database = Application.DocumentManager.MdiActiveDocument.Database)  
  using (var transaction = database.TransactionManager.StartTransaction()) { 
    // Open the block table for read  
    var blockTable = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead);  

    // Open the block table record model space for write  
    var blockTableRecord = (BlockTableRecord)transaction.GetObject(blockTable[BlockTableRecord.ModelSpace], OpenMode.ForWrite);  

    // Run specific logic
    action(transaction, blockTableRecord);

    transaction.Commit();  
  }  
}  

(остальная часть кода будет такой же)

PerformActionOnBlockTable можно повторно использовать для запуска произвольной логики с использованием транзакции и записи таблицы блоков.

person Jordão    schedule 29.09.2010
comment
Да, это тоже будет работать! Полностью пропустил этот подход, +1 за его уловку :) Это затруднило бы какую-либо логику для добавления элемента при необходимости. Спасибо, что нашли время ответить! - person JSprang; 30.09.2010

Можно возразить, что оба ваших примера инъекций фактически являются одним и тем же — отправка интерфейса или делегата в методе PerformAction... не имеет значения и на самом деле является делом вкуса. При этом реализация отдельного класса для фигур позволила бы классу PerformAction... быть открытым для расширения, но закрытым для модификация. Все ваши фигуры будут расширениями, и ваш метод PerformAction... никогда не должен меняться.

Как ни странно, формы являются каноническим примером принципа открытости/закрытости.

person Austin Salonen    schedule 08.10.2010
comment
Привет, Остин, рад тебя слышать и спасибо за твой вклад! Мне очень интересно покопаться в ссылке «Принцип открытости/закрытости», которой вы поделились. - person JSprang; 11.10.2010