Tách một nhóm các dự án tiện ích của thành phố thành các thành phần riêng lẻ với các phụ thuộc tùy chọn của Google


26

Qua nhiều năm sử dụng C # /. NET cho một loạt các dự án nội bộ, chúng tôi đã có một thư viện phát triển hữu cơ thành một thứ khổng lồ. Nó được gọi là "Util", và tôi chắc rằng nhiều bạn đã nhìn thấy một trong những con thú này trong sự nghiệp của bạn.

Nhiều phần của thư viện này rất độc lập và có thể được chia thành các dự án riêng biệt (mà chúng tôi muốn nguồn mở). Nhưng có một vấn đề lớn cần được giải quyết trước khi chúng có thể được phát hành dưới dạng các thư viện riêng biệt. Về cơ bản, có rất nhiều trường hợp về cái mà tôi có thể gọi là "phụ thuộc tùy chọn" giữa các thư viện này.

Để giải thích điều này tốt hơn, hãy xem xét một số mô-đun là ứng cử viên tốt để trở thành thư viện độc lập. CommandLineParserlà để phân tích dòng lệnh. XmlClassifylà để tuần tự hóa các lớp thành XML. PostBuildCheckthực hiện kiểm tra trên tổ hợp đã biên dịch và báo cáo lỗi biên dịch nếu chúng không thành công. ConsoleColoredStringlà một thư viện cho các chuỗi ký tự màu. Lingolà để dịch giao diện người dùng.

Mỗi thư viện đó có thể được sử dụng hoàn toàn độc lập, nhưng nếu chúng được sử dụng cùng nhau thì sẽ có những tính năng bổ sung hữu ích. Ví dụ, cả hai CommandLineParserXmlClassifyphơi bày chức năng kiểm tra sau xây dựng, đòi hỏi PostBuildCheck. Tương tự, CommandLineParsertài liệu tùy chọn cho phép được cung cấp bằng cách sử dụng chuỗi ký tự màu, yêu cầu ConsoleColoredStringvà nó hỗ trợ tài liệu có thể dịch qua Lingo.

Vì vậy, điểm khác biệt chính là đây là những tính năng tùy chọn . Người ta có thể sử dụng một trình phân tích cú pháp dòng lệnh với các chuỗi đơn giản, không màu, mà không cần dịch tài liệu hoặc thực hiện bất kỳ kiểm tra sau khi xây dựng. Hoặc người ta có thể làm cho tài liệu có thể dịch nhưng vẫn không bị đổi màu. Hoặc cả màu và dịch. V.v.

Nhìn qua thư viện "Util" này, tôi thấy rằng hầu như tất cả các thư viện có khả năng phân tách đều có các tính năng tùy chọn như vậy buộc chúng vào các thư viện khác. Nếu tôi thực sự yêu cầu các thư viện đó làm phụ thuộc thì công cụ này hoàn toàn không được gỡ rối: về cơ bản bạn vẫn yêu cầu tất cả các thư viện nếu bạn chỉ muốn sử dụng một thư viện.

Có bất kỳ cách tiếp cận được thiết lập để quản lý các phụ thuộc tùy chọn như vậy trong .NET không?


2
Ngay cả khi các thư viện phụ thuộc lẫn nhau, vẫn có thể có một số lợi ích trong việc tách chúng thành các thư viện mạch lạc nhưng riêng biệt, mỗi thư viện chứa một danh mục chức năng rộng.
Robert Harvey

Câu trả lời:


20

Tái cấu trúc từ từ.

Yêu cầu việc này sẽ mất một chút thời gian để hoàn thành và có thể xảy ra qua một số lần lặp trước khi bạn có thể loại bỏ hoàn toàn cụm Utils của mình .

Cách tiếp cận tổng thể:

  1. Trước tiên hãy dành chút thời gian và suy nghĩ xem bạn muốn các hội đồng tiện ích này trông như thế nào khi bạn hoàn thành. Đừng lo lắng quá nhiều về mã hiện tại của bạn, hãy nghĩ về mục tiêu cuối cùng. Ví dụ: bạn có thể muốn có:

    • MyCompany.Utilities.Core (Chứa thuật toán, ghi nhật ký, v.v.)
    • MyCompany.Utilities.UI (Vẽ mã, v.v.)
    • MyCompany.Utilities.UI.WinForms (System.Windows.Forms liên quan đến mã, điều khiển tùy chỉnh, v.v.)
    • MyCompany.Utilities.UI.WPF (mã liên quan đến WPF, các lớp cơ sở MVVM).
    • MyCompany.Utilities.Serialization (Mã tuần tự hóa).
  2. Tạo các dự án trống cho từng dự án này và tạo các tham chiếu dự án phù hợp (tham chiếu UI Core, UI.WinForms tham chiếu UI), v.v.

  3. Di chuyển bất kỳ trái cây treo thấp nào (các lớp hoặc phương pháp không gặp phải các vấn đề phụ thuộc) từ tổ hợp Utils của bạn sang các cụm mục tiêu mới.

  4. Nhận một bản sao của Tái cấu trúc NDepend và Martin Fowler để bắt đầu phân tích tập hợp Utils của bạn để bắt đầu làm việc với những người khó khăn hơn. Hai kỹ thuật sẽ hữu ích:

    • Trong nhiều trường hợp, bạn sẽ cần Đảo ngược Điều khiển thông qua các phương tiện giao diện, đại biểu hoặc sự kiện .
    • Trong trường hợp phụ thuộc "tùy chọn" của bạn , xem bên dưới.

Xử lý các giao diện tùy chọn

Hoặc một hội đồng tham chiếu một hội đồng khác, hoặc nó không. Cách duy nhất khác để sử dụng chức năng trong một tổ hợp không liên kết là thông qua một giao diện được tải thông qua sự phản chiếu từ một lớp chung. Nhược điểm của nó là lắp ráp lõi của bạn sẽ cần chứa các giao diện cho tất cả các tính năng được chia sẻ, nhưng nhược điểm là bạn có thể triển khai các tiện ích của mình khi cần mà không cần "wad" các tệp DLL tùy thuộc vào từng kịch bản triển khai. Dưới đây là cách tôi sẽ xử lý trường hợp này, sử dụng chuỗi màu làm ví dụ:

  1. Đầu tiên, xác định các giao diện phổ biến trong cụm lõi của bạn:

    nhập mô tả hình ảnh ở đây

    Ví dụ: IStringColorergiao diện sẽ như sau:

     namespace MyCompany.Utilities.Core.OptionalInterfaces
     {
         public interface IStringColorer
         {
             string Decorate(string s);
         }
     }
    
  2. Sau đó, thực hiện giao diện trong lắp ráp với tính năng. Ví dụ, StringColorerlớp sẽ trông như sau:

    using MyCompany.Utilities.Core.OptionalInterfaces;
    namespace MyCompany.Utilities.Console
    {
        class StringColorer : IStringColorer
        {
            #region IStringColorer Members
    
            public string Decorate(string s)
            {
                return "*" + s + "*";   //TODO: implement coloring
            }
    
            #endregion
        }
    }
    
  3. Tạo một lớp PluginFinder(hoặc có thể Interface Downloader là một tên tốt hơn trong trường hợp này) có thể tìm thấy các giao diện từ các tệp DLL trong thư mục hiện tại. Đây là một ví dụ đơn giản. Theo lời khuyên của Per @ EdWoodcock (và tôi đồng ý), khi các dự án của bạn phát triển, tôi sẽ đề xuất sử dụng một trong các khung Tiêm phụ thuộc có sẵn ( Trình định vị Serivce chung với UnitySpring.NET ) để triển khai mạnh mẽ hơn với "nâng cao hơn" tính năng đó ", còn được gọi là Mẫu Định vị dịch vụ . Bạn có thể sửa đổi nó cho phù hợp với nhu cầu của bạn.

    using System;
    using System.Linq;
    using System.IO;
    using System.Reflection;
    
    namespace UtilitiesCore
    {
        public static class PluginFinder
        {
            private static bool _loadedAssemblies;
    
            public static T FindInterface<T>() where T : class
            {
                if (!_loadedAssemblies)
                    LoadAssemblies();
    
                //TODO: improve the performance vastly by caching RuntimeTypeHandles
    
                foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    foreach (Type type in assembly.GetTypes())
                    {
                        if (type.IsClass && typeof(T).IsAssignableFrom(type))
                            return Activator.CreateInstance(type) as T;
                    }
                }
    
                return null;
            }
    
            private static void LoadAssemblies()
            {
                foreach (FileInfo file in new DirectoryInfo(Directory.GetCurrentDirectory()).GetFiles())
                {
                    if (file.Extension != ".DLL")
                        continue;
    
                    if (!AppDomain.CurrentDomain.GetAssemblies().Any(a => a.Location == file.FullName))
                    {
                        try
                        {
                            //TODO: perhaps filter by certain known names
                            Assembly.LoadFrom(file.FullName);
                        }
                        catch { }
                    }
                }
            }
        }
    }
    
  4. Cuối cùng, sử dụng các giao diện này trong các hội đồng khác của bạn bằng cách gọi phương thức FindInterface. Đây là một ví dụ về CommandLineParser:

    static class CommandLineParser
    {
        public static string ParseCommandLine(string commandLine)
        {
            string parsedCommandLine = ParseInternal(commandLine);
    
            IStringColorer colorer = PluginFinder.FindInterface<IStringColorer>();
    
            if(colorer != null)
                parsedCommandLine = colorer.Decorate(parsedCommandLine);
    
            return parsedCommandLine;
        }
    
        private static string ParseInternal(string commandLine)
        {
            //TODO: implement parsing as desired
            return commandLine;
        }
    

    }

QUAN TRỌNG NHẤT: Kiểm tra, thử nghiệm, kiểm tra giữa mỗi thay đổi.


Tôi đã thêm ví dụ! :-)
Kevin McCormick

1
Lớp Plugin Downloader đó trông đáng ngờ giống như trình xử lý DI tự động của chính bạn (sử dụng mẫu ServiceLocator), nhưng đây là lời khuyên âm thanh. Có lẽ bạn sẽ tốt hơn khi chỉ OP vào một cái gì đó như Unity, vì điều đó sẽ không có vấn đề với nhiều triển khai của một giao diện cụ thể trong các thư viện (StringColourer vs StringColourerWithHtmlWrapper hoặc bất cứ điều gì).
Ed James

@EdWoodcock Điểm tốt Ed và tôi không thể tin rằng mình đã không nghĩ đến mẫu Trình định vị dịch vụ khi viết bài này. Plugin Downloader chắc chắn là một triển khai chưa trưởng thành và khung DI chắc chắn sẽ hoạt động ở đây.
Kevin McCormick

Tôi đã trao cho bạn tiền thưởng cho nỗ lực này, nhưng chúng tôi sẽ không đi theo con đường này. Chia sẻ tập hợp các giao diện cốt lõi có nghĩa là chúng tôi chỉ thành công trong việc loại bỏ các triển khai, nhưng vẫn có một thư viện chứa các giao diện ít liên quan (liên quan đến các phụ thuộc tùy chọn, như trước đây). Việc thiết lập bây giờ phức tạp hơn nhiều với rất ít lợi ích cho các thư viện nhỏ như thế này. Sự phức tạp thêm có thể đáng giá cho các dự án hài hước, nhưng không phải là những dự án này.
Roman Starkov

@romkyns Vậy bạn đang đi theo con đường nào? Rời khỏi nó như là? :)
Tối đa

5

Bạn có thể sử dụng các giao diện được khai báo trong một thư viện bổ sung.

Cố gắng giải quyết hợp đồng (lớp thông qua giao diện) bằng cách sử dụng nội xạ phụ thuộc (MEF, Unity, v.v.). Nếu không tìm thấy, đặt nó để trả về một thể hiện null.
Sau đó kiểm tra xem trường hợp có rỗng không, trong trường hợp đó bạn không thực hiện các chức năng bổ sung.

Điều này đặc biệt dễ thực hiện với MEF, vì đó là sách giáo khoa sử dụng cho nó.

Nó sẽ cho phép bạn biên dịch các thư viện, với chi phí chia chúng thành n + 1 dlls.

HTH.


Điều này nghe có vẻ gần đúng - nếu chỉ không có thêm một DLL, mà về cơ bản giống như một bộ xương của bản gốc. Tất cả các triển khai đã được tách ra, nhưng vẫn còn một "bộ xương". Tôi cho rằng có một số lợi thế, nhưng tôi không tin rằng những lợi thế đó vượt xa tất cả các chi phí cho bộ thư viện đặc biệt này ...
Roman Starkov

Ngoài ra, bao gồm toàn bộ khung hoàn toàn là một bước lùi; thư viện này tương đương với quy mô của một trong những khung đó, hoàn toàn phủ nhận lợi ích. Nếu có bất cứ điều gì, tôi chỉ cần sử dụng một chút sự phản chiếu để xem có triển khai hay không, vì chỉ có thể có từ 0 đến 1, và không cần cấu hình bên ngoài.
Roman Starkov

2

Tôi nghĩ rằng tôi đã đăng tùy chọn khả thi nhất mà chúng tôi đã đưa ra cho đến nay, để xem những suy nghĩ là gì.

Về cơ bản, chúng tôi sẽ tách từng thành phần thành một thư viện không có tài liệu tham khảo; tất cả các mã yêu cầu tham chiếu sẽ được đặt vào một #if/#endifkhối có tên thích hợp. Ví dụ, mã trong CommandLineParserđó xử lý ConsoleColoredStrings sẽ được đặt vào #if HAS_CONSOLE_COLORED_STRING.

Bất kỳ giải pháp nào muốn bao gồm chỉ CommandLineParsercó thể dễ dàng làm như vậy, vì không có phụ thuộc thêm. Tuy nhiên, nếu giải pháp cũng bao gồm ConsoleColoredStringdự án, lập trình viên hiện có tùy chọn:

  • thêm một tài liệu tham khảo trong CommandLineParserđểConsoleColoredString
  • thêm HAS_CONSOLE_COLORED_STRINGđịnh nghĩa vào CommandLineParsertệp dự án.

Điều này sẽ làm cho các chức năng có liên quan có sẵn.

Có một số vấn đề với điều này:

  • Đây là một giải pháp chỉ có nguồn; mọi người tiêu dùng của thư viện phải bao gồm nó như một mã nguồn; chúng không thể chỉ bao gồm một nhị phân (nhưng đây không phải là một yêu cầu tuyệt đối cho chúng tôi).
  • Tệp dự án thư viện của thư viện nhận được một vài chỉnh sửa cụ thể về giải pháp và không rõ ràng chính xác cách thức thay đổi này được cam kết với SCM.

Khá là không đẹp, nhưng vẫn vậy, đây là lần gần nhất chúng tôi nghĩ ra.

Một ý tưởng khác mà chúng tôi đã xem xét là sử dụng các cấu hình dự án thay vì yêu cầu người dùng chỉnh sửa tệp dự án thư viện. Nhưng điều này hoàn toàn không khả thi trong VS2010 vì nó thêm tất cả các cấu hình dự án vào giải pháp không mong muốn .


1

Tôi sẽ giới thiệu cuốn sách Phát triển ứng dụng Brownfield trong .Net . Hai chương liên quan trực tiếp là 8 & 9. Chương 8 nói về việc chuyển tiếp ứng dụng của bạn, trong khi chương 9 nói về việc phụ thuộc thuần hóa, đảo ngược kiểm soát và tác động của việc này đối với việc kiểm tra.


1

Tiết lộ đầy đủ, tôi là một chàng trai Java. Vì vậy, tôi hiểu rằng có lẽ bạn không tìm kiếm các công nghệ tôi sẽ đề cập ở đây. Nhưng các vấn đề là như nhau, vì vậy có lẽ nó sẽ chỉ cho bạn đi đúng hướng.

Trong Java, có một số hệ thống xây dựng hỗ trợ cho ý tưởng về kho lưu trữ tạo tác tập trung chứa các "tạo tác" được xây dựng - theo hiểu biết của tôi, nó có phần giống với GAC trong .NET (vui lòng thực thi sự thiếu hiểu biết của tôi nếu đó là một giải phẫu căng thẳng) nhưng nhiều hơn thế bởi vì nó được sử dụng để tạo ra các bản dựng lặp lại độc lập tại bất kỳ thời điểm nào.

Dù sao, một tính năng khác được hỗ trợ (ví dụ trong Maven) là ý tưởng về sự phụ thuộc TÙY CHỌN, sau đó tùy thuộc vào các phiên bản hoặc phạm vi cụ thể và có khả năng loại trừ phụ thuộc bắc cầu. Điều này nghe với tôi như những gì bạn đang tìm kiếm, nhưng tôi có thể sai. Hãy xem trang giới thiệu này về quản lý phụ thuộc từ Maven với một người bạn biết Java và xem các vấn đề nghe có quen không. Điều này sẽ cho phép bạn xây dựng ứng dụng của mình và xây dựng nó có hoặc không có các phụ thuộc này.

Ngoài ra còn có các cấu trúc nếu bạn cần kiến ​​trúc thực sự năng động, có thể cắm được; một công nghệ cố gắng giải quyết hình thức giải quyết phụ thuộc thời gian chạy này là OSGI. Đây là công cụ đằng sau hệ thống plugin của Eclipse . Bạn sẽ thấy nó có thể hỗ trợ các phụ thuộc tùy chọn và phạm vi phiên bản tối thiểu / tối đa. Mức độ mô đun hóa thời gian chạy này áp đặt một số lượng lớn các ràng buộc đối với bạn và cách bạn phát triển. Hầu hết mọi người có thể nhận được với mức độ mô-đun Maven cung cấp.

Một ý tưởng khả thi khác mà bạn có thể xem xét đó có thể là những mệnh lệnh đơn giản hơn để thực hiện cho bạn là sử dụng kiểu kiến ​​trúc ống và bộ lọc. Đây phần lớn là những gì đã làm cho UNIX trở thành một hệ sinh thái thành công lâu đời, tồn tại và phát triển trong nửa thế kỷ. Hãy xem bài viết này về Ống và Bộ lọc trong .NET để biết một số ý tưởng về cách triển khai loại mẫu này trong khung của bạn.


0

Có lẽ cuốn sách "Thiết kế phần mềm C ++ quy mô lớn" của John Lakos rất hữu ích (tất nhiên là C # và C ++ hoặc không giống nhau, nhưng bạn có thể chắt lọc các kỹ thuật hữu ích từ cuốn sách).

Về cơ bản, yếu tố lại và di chuyển chức năng sử dụng hai hoặc nhiều thư viện thành một thành phần riêng biệt phụ thuộc vào các thư viện này. Nếu cần, sử dụng các kỹ thuật như các loại mờ, vv

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.