官术网_书友最值得收藏!

How it works...

Using traits instead of interfaces and other object-oriented constructs has many implications for the general architecture. In fact, common architectural thinking will likely lead to more complex and verbose code that may perform worse on top of that! Let's examine popular object-oriented principles from the Gang of Four's book, Design Patterns (1994):

  • Program to an interface not to an implementation: This principle requires some thinking in Rust. With the 2018 edition, functions can accept an impl MyTrait parameter, where earlier versions had to use Box<MyTrait> or o: T and later where T: MyTrait, all of which have their own issues. It's a trade-off for every project: either less complex abstractions with the concrete type or more generics and other complexity for cleaner encapsulation. 
  • Favor object composition over class inheritance: While this only applies to some extent (there is no inheritance in Rust), object composition is still something that seems like a good idea. Add trait type properties to your struct instead of the actual type. However, unless it's a boxed trait (that is, slower dynamic dispatch), there is no way for the compiler to know exactly the size it should reserve—a type instance could have 10 times the size of the trait from other things. Therefore, a reference is required. Unfortunately, though, that introduces explicit lifetimes—making the code a lot more verbose and complex to handle.

Rust clearly favors splitting off behavior from data, where the former goes into a trait and the latter remains with the original struct. In this recipe, KeyValueConfigService did not have to manage any data—its task was to read and write Config instances.

After creating these structs in step 2, we created the behavior traits in step 3. There, we split the tasks off into two individual traits to keep them small and manageable. Anything can implement these traits and thereby acquire the capabilities of writing or reading config files or retrieving a specific value by its key. 

We kept the functions on the trait generic as well to allow for easy unit testing (we can use Vec<T> instead of faking files). Using Rust's impl trait feature, we only care about the fact that std::io::Read and std::io::Write have been implemented by whatever is passed in.

Step 4 implements the traits in an individual impl block for the structs. The ConfigReader strategy is naive: split into lines, split those lines at the first = character, and declare the left- and right-hand parts key and value respectively. The ValueGetter implementation then walks through the key-value pairs to find the requested key. We preferred Vec with String tuples here for simplicity, for example, HashMap can improve performance substantially.

The tests implemented in step 5 provide an overview of how the system works and how we seamlessly use the types by the traits they implement. Vec doubles as a read/write stream, no type-casting required. To make sure the tests actually run through, we run cargo test in step 6

 After this lesson on structuring code, let's move on to the next recipe.

主站蜘蛛池模板: 石首市| 丹阳市| 宽甸| 北票市| 安远县| 河池市| 洛浦县| 门源| 中西区| 镇江市| 宿迁市| 伊金霍洛旗| 昭通市| 武夷山市| 白水县| 开阳县| 宁津县| 二连浩特市| 巢湖市| 启东市| 花莲县| 梁山县| 个旧市| 建宁县| 云浮市| 溧水县| 墨竹工卡县| 曲阜市| 交城县| 浠水县| 龙岩市| 内江市| 兴安盟| 吉安县| 腾冲县| 叶城县| 汤原县| 固始县| 察哈| 石河子市| 德州市|