Nơi để tải và lưu trữ các cài đặt từ một tập tin?


9

Tôi nghĩ rằng câu hỏi này nên áp dụng cho hầu hết các chương trình tải cài đặt từ một tệp. Câu hỏi của tôi là từ quan điểm lập trình, và đó thực sự là cách đối phó với việc tải các cài đặt từ một tệp theo các lớp khác nhau và khả năng truy cập. Ví dụ:

  • Nếu một chương trình có một settings.initệp đơn giản , nội dung của nó có nên được tải trong một load()phương thức của một lớp hay có lẽ là hàm tạo không?
  • Các giá trị nên được lưu trữ trong public staticcác biến, hay nên có staticcác phương thức để lấy và đặt thuộc tính?
  • Điều gì sẽ xảy ra trong trường hợp tệp không tồn tại hoặc không thể đọc được? Làm thế nào bạn có thể cho phần còn lại của chương trình biết rằng nó không thể có được những tính chất đó?
  • Vân vân.

Tôi hy vọng tôi đang hỏi điều này ở đúng nơi ở đây. Tôi muốn đặt câu hỏi là bất khả tri ngôn ngữ nhất có thể, nhưng tôi chủ yếu tập trung vào các ngôn ngữ có những thứ như kế thừa - đặc biệt là Java và C # .NET.


1
Đối với .NET, tốt nhất là sử dụng App.config và lớp System.Configuration.ConfigurationManager để nhận cài đặt
Gibson

@Gibson Tôi chỉ mới bắt đầu bằng .NET nên bạn có thể liên kết tôi với bất kỳ hướng dẫn tốt nào cho lớp đó không?
Andy

Stackoverflow có rất nhiều câu trả lời về nó. Tìm kiếm nhanh chóng cho thấy: stackoverflow.com/questions/114527/...stackoverflow.com/questions/13043530/... Cũng sẽ có rất nhiều thông tin trên MSDN, bắt đầu từ đây msdn.microsoft.com/en-us/library/ms228063(v = vs.100) .aspxmsdn.microsoft.com/en-us/l
Gibson

@Gibson Cảm ơn bạn đã liên kết. Chúng sẽ rất hữu ích.
Andy

Câu trả lời:


8

Đây thực sự là một câu hỏi quan trọng và nó thường được thực hiện sai vì nó không đủ tầm quan trọng mặc dù nó là một phần cốt lõi của khá nhiều ứng dụng. Dưới đây là hướng dẫn của tôi:

Lớp cấu hình của bạn, chứa tất cả các cài đặt sẽ chỉ là một kiểu dữ liệu cũ, struct / class:

class Config {
    int prop1;
    float prop2;
    SubConfig subConfig;
}

Nó không cần phải có các phương thức và không nên liên quan đến kế thừa (trừ khi đó là lựa chọn duy nhất bạn có trong ngôn ngữ của mình để thực hiện một trường biến thể - xem đoạn tiếp theo). Nó có thể và nên sử dụng thành phần để nhóm các cài đặt thành các lớp cấu hình cụ thể nhỏ hơn (ví dụ: subConfig ở trên). Nếu bạn làm theo cách này, sẽ rất lý tưởng để vượt qua trong các bài kiểm tra đơn vị và ứng dụng nói chung vì nó sẽ có các phụ thuộc tối thiểu.

Bạn có thể sẽ cần sử dụng các loại biến thể, trong trường hợp cấu hình cho các thiết lập khác nhau có cấu trúc không đồng nhất. Chúng tôi chấp nhận rằng bạn sẽ cần đặt một động lực vào một lúc nào đó khi bạn đọc giá trị để chuyển nó sang lớp cấu hình (phụ) bên phải và không nghi ngờ gì điều này sẽ phụ thuộc vào cài đặt cấu hình khác.

Bạn không nên lười biếng trong việc nhập tất cả các cài đặt dưới dạng các trường bằng cách thực hiện điều này:

class Config {
    Dictionary<string, string> values;
};

Điều này rất hấp dẫn vì nó có nghĩa là bạn có thể viết một lớp tuần tự hóa tổng quát mà không cần biết nó đang xử lý những lĩnh vực nào, nhưng nó sai và tôi sẽ giải thích tại sao ngay lập tức.

Việc tuần tự hóa cấu hình được thực hiện trong một lớp hoàn toàn riêng biệt. Bất kể API hoặc thư viện nào bạn sử dụng để thực hiện việc này, phần thân của chức năng tuần tự hóa của bạn sẽ chứa các mục cơ bản tương đương với bản đồ từ đường dẫn / khóa trong tệp đến trường trên đối tượng. Một số ngôn ngữ cung cấp khả năng hướng nội tốt và có thể thực hiện điều này cho bạn, những ngôn ngữ khác bạn sẽ phải viết rõ ràng ánh xạ, nhưng điều quan trọng là bạn chỉ phải viết ánh xạ một lần. Ví dụ, hãy xem xét trích xuất này, tôi đã điều chỉnh từ tài liệu phân tích cú pháp tùy chọn chương trình tăng cường c ++:

struct Config {
   int opt;
} conf;
po::options_description desc("Allowed options");
desc.add_options()
    ("optimization", po::value<int>(&conf.opt)->default_value(10);

Lưu ý rằng về cơ bản dòng cuối cùng nói "tối ưu hóa" ánh xạ tới Config :: opt và cũng có một tuyên bố về loại mà bạn mong đợi. Bạn muốn đọc cấu hình không thành công nếu kiểu không như bạn mong đợi, nếu tham số trong tệp không thực sự là dấu phẩy hoặc int hoặc không tồn tại. Lỗi thất bại sẽ xảy ra khi bạn đọc tệp vì vấn đề là do định dạng / xác thực của tệp và bạn nên ném mã ngoại trừ / trả lại và báo cáo vấn đề chính xác. Bạn không nên trì hoãn điều này để sau này trong chương trình. Đó là lý do tại sao bạn không nên bắt tất cả các kiểu từ điển Conf như đã đề cập ở trên, điều này sẽ không thất bại khi tệp được đọc - vì quá trình truyền bị trì hoãn cho đến khi cần giá trị.

Bạn nên làm cho lớp Cấu hình chỉ đọc theo kiểu nào đó - thiết lập nội dung của lớp một lần khi bạn tạo và khởi tạo nó từ tệp. Nếu bạn cần phải có các cài đặt động trong ứng dụng của mình thay đổi, cũng như các cài đặt không thay đổi, bạn nên có một lớp riêng để xử lý các cài đặt động thay vì cố gắng cho phép các bit của lớp cấu hình của bạn không chỉ đọc .

Lý tưởng nhất là bạn đọc tệp ở một nơi trong chương trình của bạn, tức là bạn chỉ có một thể hiện của " ConfigReader". Tuy nhiên, nếu bạn đang vật lộn để đưa phiên bản Cấu hình được chuyển đến nơi bạn cần, thì tốt hơn là nên có một Trình cấu hình thứ hai hơn là giới thiệu một cấu hình toàn cầu (mà tôi đoán là OP có nghĩa là "tĩnh" "), Điều này dẫn tôi đến điểm tiếp theo của tôi:

Tránh bài hát tiếng còi quyến rũ của người độc thân: "Tôi sẽ cứu bạn phải vượt qua lớp học đó, tất cả các nhà xây dựng của bạn sẽ đáng yêu và sạch sẽ. Tiếp tục, nó sẽ rất dễ dàng." Sự thật là với một kiến ​​trúc có thể kiểm tra được thiết kế tốt, bạn sẽ khó có thể vượt qua lớp Cấu hình hoặc các phần của nó qua nhiều lớp ứng dụng của bạn. Những gì bạn sẽ tìm thấy, trong lớp cấp cao nhất, hàm main () hoặc bất cứ thứ gì, bạn sẽ làm sáng tỏ các giá trị riêng lẻ, mà bạn sẽ cung cấp cho các lớp thành phần của mình dưới dạng các đối số mà sau đó bạn đặt lại với nhau (phụ thuộc thủ công mũi tiêm). Một conf đơn / toàn cầu / tĩnh sẽ làm cho đơn vị kiểm tra ứng dụng của bạn khó thực hiện và hiểu hơn - ví dụ: nó sẽ gây nhầm lẫn cho các nhà phát triển mới cho nhóm của bạn, những người sẽ không biết họ phải đặt trạng thái toàn cầu để kiểm tra nội dung.

Nếu ngôn ngữ của bạn hỗ trợ các thuộc tính, bạn nên sử dụng chúng cho mục đích này. Lý do là nó có nghĩa là sẽ rất dễ dàng để thêm các cài đặt cấu hình 'dẫn xuất' phụ thuộc vào một hoặc nhiều cài đặt khác. ví dụ

int Prop1 { get; }
int Prop2 { get; }
int Prop3 { get { return Prop1*Prop2; }

Nếu ngôn ngữ của bạn không hỗ trợ thành ngữ thuộc tính, nó có thể có một cách giải quyết để đạt được hiệu quả tương tự hoặc bạn chỉ cần tạo một lớp bao bọc cung cấp các cài đặt phần thưởng. Nếu bạn không thể mang lại lợi ích của các thuộc tính thì thật lãng phí thời gian để viết thủ công và sử dụng getters / setters chỉ đơn giản cho mục đích làm hài lòng một vị thần OO. Bạn sẽ tốt hơn với một lĩnh vực cũ đơn giản.

Bạn có thể cần một hệ thống để hợp nhất và lấy nhiều cấu hình từ các vị trí khác nhau theo thứ tự ưu tiên. Thứ tự ưu tiên đó phải được xác định rõ và được hiểu bởi tất cả các nhà phát triển / người dùng, ví dụ như xem xét sổ đăng ký HKEY_CURRENT_USER / HKEY_LOCAL_MACHINE. Bạn nên thực hiện kiểu chức năng này để bạn có thể giữ cấu hình của mình chỉ đọc, tức là:

final_conf = merge(user_conf, machine_conf)

thay vì:

conf.update(user_conf)

Cuối cùng tôi cũng nên thêm rằng tất nhiên nếu khung / ngôn ngữ đã chọn của bạn cung cấp các cơ chế cấu hình nổi tiếng, tích hợp sẵn của riêng bạn, bạn nên xem xét các lợi ích của việc sử dụng đó thay vì tự lăn.

Vì thế. Rất nhiều khía cạnh để xem xét - làm cho đúng và nó sẽ ảnh hưởng sâu sắc đến kiến ​​trúc ứng dụng của bạn, giảm lỗi, khiến mọi thứ dễ kiểm tra và buộc bạn phải sử dụng thiết kế tốt ở nơi khác.


+1 Cảm ơn bạn đã trả lời. Trước đây khi tôi lưu trữ cài đặt người dùng, tôi vừa đọc từ một tệp như .inivậy để nó có thể đọc được, nhưng bạn có gợi ý rằng tôi nên tuần tự hóa một lớp với các biến trong không?
Andy

.ini rất dễ phân tích cú pháp và cũng có rất nhiều API bao gồm .ini như một định dạng có thể. Tôi khuyên bạn nên sử dụng các API như vậy để giúp bạn thực hiện công việc phân tích cú pháp văn bản nhưng kết quả cuối cùng sẽ là bạn đã khởi tạo một lớp POD. Tôi sử dụng thuật ngữ tuần tự theo nghĩa chung là sao chép vào hoặc đọc trong các trường của một lớp từ một định dạng nào đó, không phải là định nghĩa cụ thể hơn về việc xâu chuỗi một lớp trực tiếp sang / từ nhị phân (như thường được hiểu là nói java.io.Serializable ).
Benedict

Bây giờ tôi hiểu rõ hơn một chút. Vì vậy, về cơ bản, bạn đang nói có một lớp với tất cả các trường / cài đặt và có một lớp khác để đặt các giá trị trong lớp đó từ tệp? Sau đó, làm thế nào tôi có được các giá trị từ lớp đầu tiên trong các lớp khác?
Andy

Tùy từng trường hợp. Một số lớp cấp cao nhất của bạn có thể chỉ cần một tham chiếu đến toàn bộ cá thể Cấu hình được truyền vào chúng nếu chúng dựa vào quá nhiều tham số. Các lớp khác chỉ cần một hoặc hai tham số, nhưng không cần phải ghép chúng với đối tượng Cấu hình (làm cho chúng dễ kiểm tra hơn), chỉ cần truyền một vài tham số qua ứng dụng của bạn. Như đã đề cập trong câu trả lời của tôi, nếu bạn xây dựng với kiến ​​trúc định hướng có thể kiểm tra / DI nhận được các giá trị mà bạn cần chúng thường sẽ không khó. Sử dụng truy cập toàn cầu lúc nguy hiểm của bạn.
Benedict

Vì vậy, làm thế nào bạn có thể đề xuất rằng đối tượng cấu hình được truyền cho các lớp nếu kế thừa không cần phải tham gia? Thông qua một nhà xây dựng? Vì vậy, trong phương thức chính của chương trình, lớp trình đọc được khởi tạo để lưu trữ các giá trị trong lớp Cấu hình, sau đó phương thức chính chuyển đối tượng Cấu hình hoặc các biến riêng lẻ sang các lớp khác?
Andy

4

Nói chung (theo ý kiến ​​của tôi), tốt nhất là để ứng dụng xử lý cách cấu hình được lưu trữ và chuyển cấu hình vào các mô-đun của bạn. Điều này cho phép linh hoạt trong cách lưu cài đặt để bạn có thể nhắm mục tiêu tệp hoặc dịch vụ web hoặc cơ sở dữ liệu hoặc ...

Thêm vào đó, nó đặt gánh nặng "những gì xảy ra khi mọi thứ thất bại" trên ứng dụng, người hiểu rõ nhất sự thất bại đó có nghĩa là gì.

nó giúp việc kiểm tra đơn vị dễ dàng hơn khi bạn chỉ có thể truyền vào một đối tượng cấu hình thay vì phải chạm vào hệ thống tệp hoặc xử lý các vấn đề tương tranh được giới thiệu bởi truy cập tĩnh.


Cảm ơn câu trả lời của bạn nhưng tôi không hiểu lắm. Tôi đang nói về việc viết ứng dụng, vì vậy không có gì để xử lý cấu hình, và do đó, là một lập trình viên, tôi biết thất bại có nghĩa là gì.
Andy

@andy - chắc chắn, nhưng nếu các mô-đun truy cập cấu hình trực tiếp, họ không biết bối cảnh họ đang làm việc trong bối cảnh nào, vì vậy họ không thể xác định cách xử lý các sự cố cấu hình. Nếu ứng dụng đang thực hiện tải, nó có thể xử lý mọi vấn đề, vì nó biết ngữ cảnh. Một số ứng dụng có thể muốn thất bại với giá trị mặc định, một số ứng dụng khác muốn hủy bỏ, v.v.
Telastyn

Chỉ cần làm rõ ở đây, bởi các mô-đun bạn đang đề cập đến các lớp, hoặc các phần mở rộng thực tế cho một chương trình? Bởi vì tôi đang hỏi về nơi tốt nhất để lưu trữ các cài đặt thực sự bên trong mã cho chương trình chính và cách cho phép các lớp khác truy cập vào các cài đặt. Vì vậy, tôi muốn tải một tệp cấu hình và sau đó lưu trữ cài đặt ở đâu đó / bằng cách nào đó để tôi không phải tiếp tục tham khảo tệp.
Andy

0

Nếu bạn đang sử dụng .NET để lập trình các lớp, bạn có các tùy chọn khác nhau như Tài nguyên, web.config hoặc thậm chí là một tệp tùy chỉnh.

Nếu bạn sử dụng Tài nguyên hoặc web.config, dữ liệu thực sự được lưu trữ trong tệp XML, nhưng, quá trình tải nhanh hơn.

Lấy dữ liệu từ các tệp này và lưu trữ chúng ở một vị trí khác sẽ giống như việc sử dụng bộ nhớ gấp đôi vì chúng được tải vào bộ nhớ theo mặc định.

Đối với bất kỳ tệp hoặc ngôn ngữ lập trình nào khác, câu trả lời trên của Benedict sẽ hoạt động.


Cảm ơn câu trả lời của bạn và xin lỗi vì trả lời chậm. Sử dụng Tài nguyên sẽ là một lựa chọn tốt, nhưng tôi đã nhận ra rằng có lẽ đó không phải là lựa chọn tốt nhất cho tôi khi tôi dự định phát triển cùng một chương trình bằng các ngôn ngữ khác, vì vậy tôi muốn có một tệp XML hoặc JSON nhưng đọc nó trong lớp của riêng tôi để có thể đọc cùng một tệp trong các ngôn ngữ lập trình khác.
Andy
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.