成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

細(xì)節(jié)見真章,F(xiàn)ormatter注冊中心的設(shè)計(jì)很討巧

開發(fā) 前端
本文以介紹FormatterRegistry接口為中心,重點(diǎn)研究了此接口的實(shí)現(xiàn)方式,發(fā)現(xiàn)即使小小的一枚注冊中心實(shí)現(xiàn),也蘊(yùn)藏有豐富亮點(diǎn)供以學(xué)習(xí)、CV。

[[374345]]

 你好,我是A哥(YourBatman)。

Spring設(shè)計(jì)了org.springframework.format.Formatter格式化器接口抽象,對格式化器進(jìn)行了大一統(tǒng),讓你只需要關(guān)心統(tǒng)一的API,而無需關(guān)注具體實(shí)現(xiàn),相關(guān)議題上篇文章 有詳細(xì)介紹。

Spring內(nèi)建有不少格式化器實(shí)現(xiàn),同時(shí)對它們的管理、調(diào)度使用也有專門的組件負(fù)責(zé),可謂涇渭分明,職責(zé)清晰。本文將圍繞Formatter注冊中心FormatterRegistry展開,為你介紹Spring是如何優(yōu)雅,巧妙的實(shí)現(xiàn)注冊管理的。

學(xué)習(xí)編碼是個(gè)模仿的過程,絕大多數(shù)時(shí)候你并不需要?jiǎng)?chuàng)造東西。當(dāng)然這里指的模仿并非普通的CV模式,而是取精華為己所用,本文所述巧妙設(shè)計(jì)便是精華所在,任君提取。

本文提綱


版本約定

  • Spring Framework:5.3.x
  • Spring Boot:2.4.x

正文

對Spring的源碼閱讀、分析這么多了,會(huì)發(fā)現(xiàn)對于組件管理大體思想都一樣,離不開這幾個(gè)組件:注冊中心(注冊員) + 分發(fā)器。

一龍生九子,九子各不同。雖然大體思路保持一致,但每個(gè)實(shí)現(xiàn)在其場景下都有自己的發(fā)揮空間,值得我們向而往之。

FormatterRegistry:格式化器注冊中心

field屬性格式化器的注冊表(注冊中心)。請注意:這里強(qiáng)調(diào)了field的存在,先混個(gè)眼熟,后面你將能有較深體會(huì)。

  1. public interface FormatterRegistry extends ConverterRegistry { 
  2.  
  3.  void addPrinter(Printer<?> printer); 
  4.  void addParser(Parser<?> parser); 
  5.  void addFormatter(Formatter<?> formatter); 
  6.  void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter); 
  7.  void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser); 
  8.  
  9.  void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory); 

此接口繼承自類型轉(zhuǎn)換器注冊中心ConverterRegistry,所以格式化注冊中心是轉(zhuǎn)換器注冊中心的加強(qiáng)版,是其超集,功能更多更強(qiáng)大。

❝關(guān)于類型轉(zhuǎn)換器注冊中心ConverterRegistry的詳細(xì)介紹,可翻閱本系列的這篇文章,看完后門清❞

雖然FormatterRegistry提供的添加方法挺多,但其實(shí)基本都是在描述同一個(gè)事:為指定類型fieldType添加格式化器(printer或parser),繪制成圖如下所示:


❝說明:最后一個(gè)接口方法除外,addFormatterForFieldAnnotation()和格式化注解相關(guān),因?yàn)樗浅V匾?,因此放在下文專門撰文講解❞

FormatterRegistry接口的繼承樹如下:


有了學(xué)過ConverterRegistry的經(jīng)驗(yàn),這種設(shè)計(jì)套路很容易被看穿。這兩個(gè)實(shí)現(xiàn)類按層級進(jìn)行分工:

  • FormattingConversionService:實(shí)現(xiàn)所有接口方法
  • DefaultFormattingConversionService:繼承自上面的FormattingConversionService,在其基礎(chǔ)上注冊默認(rèn)的格式化器

事實(shí)上,功能分類確實(shí)如此。本文重點(diǎn)介紹FormattingConversionService,這個(gè)類的設(shè)計(jì)實(shí)現(xiàn)上有很多討巧之處,只要你來,要你好看。

FormattingConversionService

它是FormatterRegistry接口的實(shí)現(xiàn)類,實(shí)現(xiàn)其所有接口方法。

FormatterRegistry是ConverterRegistry的子接口,而ConverterRegistry接口的所有方法均已由GenericConversionService全部實(shí)現(xiàn)了,所以可以通過繼承它來間接完成 ConverterRegistry接口方法的實(shí)現(xiàn),因此本類的繼承結(jié)構(gòu)是這樣子的(請細(xì)品這個(gè)結(jié)構(gòu)):


FormattingConversionService通過繼承GenericConversionService搞定“左半邊”(父接口ConverterRegistry);只剩“右半邊”待處理,也就是FormatterRegistry新增的接口方法。

  1. FormattingConversionService: 
  2.  
  3.  @Override 
  4.  public void addPrinter(Printer<?> printer) { 
  5.   Class<?> fieldType = getFieldType(printer, Printer.class); 
  6.   addConverter(new PrinterConverter(fieldType, printer, this)); 
  7.  } 
  8.  @Override 
  9.  public void addParser(Parser<?> parser) { 
  10.   Class<?> fieldType = getFieldType(parser, Parser.class); 
  11.   addConverter(new ParserConverter(fieldType, parser, this)); 
  12.  } 
  13.  @Override 
  14.  public void addFormatter(Formatter<?> formatter) { 
  15.   addFormatterForFieldType(getFieldType(formatter), formatter); 
  16.  } 
  17.  @Override 
  18.  public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) { 
  19.   addConverter(new PrinterConverter(fieldType, formatter, this)); 
  20.   addConverter(new ParserConverter(fieldType, formatter, this)); 
  21.  } 
  22.  @Override 
  23.  public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) { 
  24.   addConverter(new PrinterConverter(fieldType, printer, this)); 
  25.   addConverter(new ParserConverter(fieldType, parser, this)); 
  26.  } 

從接口的實(shí)現(xiàn)可以看到這個(gè)“驚天大秘密”:所有的格式化器(含Printer、Parser、Formatter)都是被當(dāng)作Converter注冊的,也就是說真正的注冊中心只有一個(gè),那就是ConverterRegistry。


格式化器的注冊管理遠(yuǎn)沒有轉(zhuǎn)換器那么復(fù)雜,因?yàn)樗腔谏蠈舆m配的思想,最終適配為Converter來完成注冊的。所以最終注冊進(jìn)去的實(shí)際是個(gè)經(jīng)由格式化器適配來的轉(zhuǎn)換器,完美復(fù)用了那套復(fù)雜的轉(zhuǎn)換器管理邏輯。

❝這種設(shè)計(jì)思路,完全可以“CV”到我們自己的編程思維里吧❞

甭管是Printer還是Parser,都會(huì)被適配為GenericConverter從而被添加到ConverterRegistry里面去,被當(dāng)作轉(zhuǎn)換器管理起來?,F(xiàn)在你應(yīng)該知道為何FormatterRegistry接口僅需提供添加方法而無需提供刪除方法了吧。

當(dāng)然嘍,關(guān)于Printer/Parser的適配實(shí)現(xiàn)亦是本文本文關(guān)注的焦點(diǎn),里面大有文章可為,let's go!

PrinterConverter:Printer接口適配器

把Printer適配為轉(zhuǎn)換器,轉(zhuǎn)換目標(biāo)為fieldType -> String。

  1. private static class PrinterConverter implements GenericConverter { 
  2.   
  3.  private final Class<?> fieldType; 
  4.  // 從Printer<?>泛型里解析出來的類型,有可能和fieldType一樣,有可能不一樣 
  5.  private final TypeDescriptor printerObjectType; 
  6.  // 實(shí)際執(zhí)行“轉(zhuǎn)換”動(dòng)作的組件 
  7.  private final Printer printer; 
  8.  private final ConversionService conversionService; 
  9.  
  10.  public PrinterConverter(Class<?> fieldType, Printer<?> printer, ConversionService conversionService) { 
  11.   ... 
  12.   // 從類上解析出泛型類型,但不一定是實(shí)際類型 
  13.   this.printerObjectType = TypeDescriptor.valueOf(resolvePrinterObjectType(printer)); 
  14.   ... 
  15.  } 
  16.  
  17.  // fieldType -> String 
  18.  @Override 
  19.  public Set<ConvertiblePair> getConvertibleTypes() { 
  20.   return Collections.singleton(new ConvertiblePair(this.fieldType, String.class)); 
  21.  } 
  22.  

既然是轉(zhuǎn)換器,重點(diǎn)當(dāng)然是它的convert轉(zhuǎn)換方法:

  1. PrinterConverter: 
  2.  
  3.  @Override 
  4.  @SuppressWarnings("unchecked"
  5.  public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { 
  6.   // 若sourceType不是printerObjectType的子類型 
  7.   // 就嘗試用conversionService轉(zhuǎn)一下類型試試 
  8.   // (也就是說:若是子類型是可直接處理的,無需轉(zhuǎn)換一趟) 
  9.   if (!sourceType.isAssignableTo(this.printerObjectType)) { 
  10.    source = this.conversionService.convert(source, sourceType, this.printerObjectType); 
  11.   } 
  12.   if (source == null) { 
  13.    return ""
  14.   } 
  15.  
  16.   // 執(zhí)行實(shí)際轉(zhuǎn)換邏輯 
  17.   return this.printer.print(source, LocaleContextHolder.getLocale()); 
  18.  } 

轉(zhuǎn)換步驟分為兩步:

1.若源類型(實(shí)際類型)不是該P(yáng)rinter類型的泛型類型的子類型的話,那就嘗試使用conversionService轉(zhuǎn)一趟

    a.例如:Printer處理的是Number類型,但是你傳入的是Person類型,這個(gè)時(shí)候conversionService就會(huì)發(fā)揮作用了

2.交由目標(biāo)格式化器Printer執(zhí)行實(shí)際的轉(zhuǎn)換邏輯


可以說Printer它可以直接轉(zhuǎn),也可以是構(gòu)建在conversionService 之上 的一個(gè)轉(zhuǎn)換器:只要源類型是我能處理的,或者經(jīng)過conversionService后能成為我能處理的類型,都能進(jìn)行轉(zhuǎn)換。有一次完美的能力復(fù)用。

說到這我估計(jì)有些小伙伴還不能理解啥意思,能解決什么問題,那么下面我分別給你用代碼舉例,加深你的了解。

準(zhǔn)備一個(gè)Java Bean:

  1. @Data 
  2. @NoArgsConstructor 
  3. @AllArgsConstructor 
  4. public class Person { 
  5.  
  6.     private Integer id; 
  7.     private String name

準(zhǔn)備一個(gè)Printer:將Integer類型加10后,再轉(zhuǎn)為String類型

  1. private static class IntegerPrinter implements Printer<Integer> { 
  2.  
  3.     @Override 
  4.     public String print(Integer object, Locale locale) { 
  5.         object += 10; 
  6.         return object.toString(); 
  7.     } 

示例一:使用Printer,無中間轉(zhuǎn)換

測試用例:

  1. @Test 
  2. public void test2() { 
  3.     FormattingConversionService formattingConversionService = new FormattingConversionService(); 
  4.     FormatterRegistry formatterRegistry = formattingConversionService; 
  5.     // 說明:這里不使用DefaultConversionService是為了避免默認(rèn)注冊的那些轉(zhuǎn)換器對結(jié)果的“干擾”,不方便看效果 
  6.     // ConversionService conversionService = new DefaultConversionService(); 
  7.     ConversionService conversionService = formattingConversionService; 
  8.  
  9.     // 注冊格式化器 
  10.     formatterRegistry.addPrinter(new IntegerPrinter()); 
  11.  
  12.     // 最終均使用ConversionService統(tǒng)一提供服務(wù)轉(zhuǎn)換 
  13.     System.out.println(conversionService.canConvert(Integer.class, String.class)); 
  14.     System.out.println(conversionService.canConvert(Person.class, String.class)); 
  15.  
  16.     System.out.println(conversionService.convert(1, String.class)); 
  17.     // 報(bào)錯(cuò):No converter found capable of converting from type [cn.yourbatman.bean.Person] to type [java.lang.String] 
  18.     // System.out.println(conversionService.convert(new Person(1, "YourBatman"), String.class)); 

運(yùn)行程序,輸出:

  1. true 
  2. false 
  3. 11 

完美。

但是,它不能完成Person -> String類型的轉(zhuǎn)換。一般來說,我們有兩種途徑來達(dá)到此目的:

1.直接方式:寫一個(gè)Person轉(zhuǎn)String的轉(zhuǎn)換器,專用

   a.缺點(diǎn)明顯:多寫一套代碼

2.組合方式(推薦):如果目前已經(jīng)有Person -> Integer的了,那我們就組合起來用就非常方便啦,下面這個(gè)例子將告訴你使用這種方式完成“需求”

   a.缺點(diǎn)不明顯:轉(zhuǎn)換器一般要求與業(yè)務(wù)數(shù)據(jù)無關(guān),因此通用性強(qiáng),應(yīng)最大可能的復(fù)用

下面示例二將幫你解決通過復(fù)用已有能力方式達(dá)到Person -> String的目的。

示例二:使用Printer,有中間轉(zhuǎn)換

基于示例一,若要實(shí)現(xiàn)Person -> String的話,只需再給寫一個(gè)Person -> Integer的轉(zhuǎn)換器放進(jìn)ConversionService里即可。

❝說明:一般來說ConversionService已經(jīng)具備很多“能力”了的,拿來就用即可。本例為了幫你說明底層原理,所以用的是一個(gè)“干凈的”ConversionService實(shí)例❞

  1. @Test 
  2. public void test2() { 
  3.     FormattingConversionService formattingConversionService = new FormattingConversionService(); 
  4.     FormatterRegistry formatterRegistry = formattingConversionService; 
  5.     // 說明:這里不使用DefaultConversionService是為了避免默認(rèn)注冊的那些轉(zhuǎn)換器對結(jié)果的“干擾”,不方便看效果 
  6.     // ConversionService conversionService = new DefaultConversionService(); 
  7.     ConversionService conversionService = formattingConversionService; 
  8.  
  9.     // 注冊格式化器 
  10.     formatterRegistry.addFormatterForFieldType(Person.class, new IntegerPrinter(), null); 
  11.     // 強(qiáng)調(diào):此處絕不能使用lambda表達(dá)式代替,否則泛型類型丟失,結(jié)果將出錯(cuò) 
  12.     formatterRegistry.addConverter(new Converter<Person, Integer>() { 
  13.         @Override 
  14.         public Integer convert(Person source) { 
  15.             return source.getId(); 
  16.         } 
  17.     }); 
  18.  
  19.     // 最終均使用ConversionService統(tǒng)一提供服務(wù)轉(zhuǎn)換 
  20.     System.out.println(conversionService.canConvert(Person.class, String.class)); 
  21.     System.out.println(conversionService.convert(new Person(1, "YourBatman"), String.class)); 

運(yùn)行程序,輸出:

  1. true 
  2. 11 

完美。

針對本例,有如下關(guān)注點(diǎn):

1.使用addFormatterForFieldType()方法注冊了IntegerPrinter,并且明確指定了處理的類型:只處理Person類型
    a.說明:IntegerPrinter是可以注冊多次分別用于處理不同類型。比如你依舊可以保留formatterRegistry.addPrinter(new IntegerPrinter());來處理Integer -> String是木問題的

2.因?yàn)镮ntegerPrinter 實(shí)際上 只能轉(zhuǎn)換 Integer -> String,因此還必須注冊一個(gè)轉(zhuǎn)換器,用于Person -> Integer橋接一下,這樣就串起來了Person -> Integer -> String。只是外部看起來這些都是IntegerPrinter做的一樣,特別工整

3.強(qiáng)調(diào):addConverter()注冊轉(zhuǎn)換器時(shí)請務(wù)必不要使用lambda表達(dá)式代替輸入,否則會(huì)失去泛型類型,導(dǎo)致出錯(cuò)

   a.若想用lambda表達(dá)式,請使用addConverter(Class,Class,Converter)這個(gè)重載方法完成注冊

ParserConverter:Parser接口適配器

把Parser適配為轉(zhuǎn)換器,轉(zhuǎn)換目標(biāo)為String -> fieldType。

  1. private static class ParserConverter implements GenericConverter { 
  2.  
  3.  private final Class<?> fieldType; 
  4.  private final Parser<?> parser; 
  5.  private final ConversionService conversionService; 
  6.  
  7.  ... // 省略構(gòu)造器 
  8.  
  9.  // String -> fieldType 
  10.  @Override 
  11.  public Set<ConvertiblePair> getConvertibleTypes() { 
  12.   return Collections.singleton(new ConvertiblePair(String.class, this.fieldType)); 
  13.  } 
  14.   

既然是轉(zhuǎn)換器,重點(diǎn)當(dāng)然是它的convert轉(zhuǎn)換方法:

  1. ParserConverter: 
  2.  
  3.  @Override 
  4.  @Nullable 
  5.  public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { 
  6.   // 空串當(dāng)null處理 
  7.   String text = (String) source; 
  8.   if (!StringUtils.hasText(text)) { 
  9.    return null
  10.   } 
  11.    
  12.   ... 
  13.   Object result = this.parser.parse(text, LocaleContextHolder.getLocale()); 
  14.   ... 
  15.    
  16.   // 解讀/轉(zhuǎn)換結(jié)果 
  17.   TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass()); 
  18.   if (!resultType.isAssignableTo(targetType)) { 
  19.    result = this.conversionService.convert(result, resultType, targetType); 
  20.   } 
  21.   return result; 
  22.  } 

轉(zhuǎn)換步驟分為兩步:

  1. 通過Parser將String轉(zhuǎn)換為指定的類型結(jié)果result(若失敗,則拋出異常)
  2. 判斷若result屬于目標(biāo)類型的子類型,直接返回,否則調(diào)用ConversionService轉(zhuǎn)換一把

可以看到它和Printer的“順序”是相反的,在返回值上做文章。同樣的,下面將用兩個(gè)例子來加深理解。

  1. private static class IntegerParser implements Parser<Integer> { 
  2.  
  3.     @Override 
  4.     public Integer parse(String text, Locale locale) throws ParseException { 
  5.         return NumberUtils.parseNumber(text, Integer.class); 
  6.     } 

示例一:使用Parser,無中間轉(zhuǎn)換

書寫測試用例:

  1. @Test 
  2. public void test3() { 
  3.     FormattingConversionService formattingConversionService = new FormattingConversionService(); 
  4.     FormatterRegistry formatterRegistry = formattingConversionService; 
  5.     ConversionService conversionService = formattingConversionService; 
  6.  
  7.     // 注冊格式化器 
  8.     formatterRegistry.addParser(new IntegerParser()); 
  9.  
  10.     System.out.println(conversionService.canConvert(String.class, Integer.class)); 
  11.     System.out.println(conversionService.convert("1"Integer.class)); 

運(yùn)行程序,輸出:

  1. true 

完美。

示例二:使用Parser,有中間轉(zhuǎn)換

下面示例輸入一個(gè)“1”字符串,出來一個(gè)Person對象(因?yàn)橛辛松厦胬拥匿亯|,這里就“直抒胸臆”了哈)。

  1. @Test 
  2. public void test4() { 
  3.     FormattingConversionService formattingConversionService = new FormattingConversionService(); 
  4.     FormatterRegistry formatterRegistry = formattingConversionService; 
  5.     ConversionService conversionService = formattingConversionService; 
  6.  
  7.     // 注冊格式化器 
  8.     formatterRegistry.addFormatterForFieldType(Person.class, null, new IntegerParser()); 
  9.     formatterRegistry.addConverter(new Converter<Integer, Person>() { 
  10.         @Override 
  11.         public Person convert(Integer source) { 
  12.             return new Person(source, "YourBatman"); 
  13.         } 
  14.     }); 
  15.  
  16.     System.out.println(conversionService.canConvert(String.class, Person.class)); 
  17.     System.out.println(conversionService.convert("1", Person.class)); 

運(yùn)行程序,啪,空指針了:

  1. java.lang.NullPointerException 
  2.  at org.springframework.format.support.FormattingConversionService$PrinterConverter.resolvePrinterObjectType(FormattingConversionService.java:179) 
  3.  at org.springframework.format.support.FormattingConversionService$PrinterConverter.<init>(FormattingConversionService.java:155) 
  4.  at org.springframework.format.support.FormattingConversionService.addFormatterForFieldType(FormattingConversionService.java:95) 
  5.  at cn.yourbatman.formatter.Demo.test4(Demo.java:86) 
  6.  ... 

根據(jù)異常棧信息,可明確原因?yàn)椋篴ddFormatterForFieldType()方法的第二個(gè)參數(shù)不能傳null,否則空指針。這其實(shí)是Spring Framework的bug,我已向社區(qū)提了issue,期待能夠被解決嘍:


為了正常運(yùn)行本例,這么改一下:

  1. // 第二個(gè)參數(shù)不傳null,用IntegerPrinter占位 
  2. formatterRegistry.addFormatterForFieldType(Person.class, new IntegerPrinter(), new IntegerParser()); 

再次運(yùn)行程序,輸出:

  1. true 
  2. Person(id=1, name=YourBatman) 

完美。

針對本例,有如下關(guān)注點(diǎn):

1.使用addFormatterForFieldType()方法注冊了IntegerParser,并且明確指定了處理的類型,用于處理Person類型

   a.也就是說此IntegerParser專門用于轉(zhuǎn)換目標(biāo)類型為Person的屬性

2.因?yàn)镮ntegerParser 實(shí)際上 只能轉(zhuǎn)換 String -> Integer,因此還必須注冊一個(gè)轉(zhuǎn)換器,用于Integer -> Person橋接一下,這樣就串起來了String -> Integer -> Person。外面看起來這些都是IntegerParser做的一樣,非常工整

3.同樣強(qiáng)調(diào):addConverter()注冊轉(zhuǎn)換器時(shí)請務(wù)必不要使用lambda表達(dá)式代替輸入,否則會(huì)失去泛型類型,導(dǎo)致出錯(cuò)

二者均持有ConversionService帶來哪些增強(qiáng)?

❝說明:關(guān)于如此重要的ConversionService你懂的,遺忘了的可乘坐電梯到這復(fù)習(xí)

對于PrinterConverter和ParserConverter來講,它們的源目的是實(shí)現(xiàn) String <-> Object,特點(diǎn)是:

  • PrinterConverter:出口必須是String類型,入口類型也已確定,即Printer的泛型類型,只能處理 T(或T的子類型) -> StringParserConverter:入口必須是String類型,出口類型也已確定,即Parser的泛型類型,只能處理 String -> T(或T的子類型)按既定“規(guī)則”,它倆的能力范圍還是蠻受限的。Spring厲害的地方就在于此,可以巧妙的通過組合的方式,擴(kuò)大現(xiàn)有組件的能力邊界。比如本利中它就在PrinterConverter/ParserConverter里分別放入了ConversionService引用,從而到這樣的效果:

ConversionService

通過能力組合協(xié)作,起到串聯(lián)作用,從而擴(kuò)大輸入/輸出“范圍”,感覺就像起到了放大鏡的效果一樣,這個(gè)設(shè)計(jì)還是很討巧的。

總結(jié)

本文以介紹FormatterRegistry接口為中心,重點(diǎn)研究了此接口的實(shí)現(xiàn)方式,發(fā)現(xiàn)即使小小的一枚注冊中心實(shí)現(xiàn),也蘊(yùn)藏有豐富亮點(diǎn)供以學(xué)習(xí)、CV。

一般來說ConversionService 天生具備非常強(qiáng)悍的轉(zhuǎn)換能力,因此實(shí)際情況是你若需要自定義一個(gè)Printer/Parser的話是大概率不需要自己再額外加個(gè)Converter轉(zhuǎn)換器的,也就是說底層機(jī)制讓你已然站在了“巨人”肩膀上。

☾本文思考題☽

看完了不一定看懂了,看懂了不一定記住了,記住了不一定掌握了。本文思考/進(jìn)階內(nèi)容:

  1. FormatterRegistry作為注冊中心只有添加方法,why?
  2. 示例中為何強(qiáng)調(diào):addConverter()注冊轉(zhuǎn)換器時(shí)請務(wù)必不要使用lambda表達(dá)式代替輸入,會(huì)有什么問題?
  3. 這種功能組合/橋接的巧妙設(shè)計(jì)方式,你腦中還能想到其它案例嗎?

本文是 A哥(YourBatman) 原創(chuàng)文章,未經(jīng)作者允許不得轉(zhuǎn)載,謝謝合作。

 

責(zé)任編輯:姜華 來源: BAT的烏托邦
相關(guān)推薦

2021-08-17 09:46:57

設(shè)計(jì)細(xì)節(jié)產(chǎn)品體驗(yàn)用戶

2012-06-27 14:19:33

2010-05-18 08:53:08

VB.NET

2013-03-15 14:47:56

ARM服務(wù)器評測ARM

2021-08-04 11:54:25

Nacos注冊中心設(shè)計(jì)

2019-02-19 15:57:07

華為云

2018-07-25 11:08:38

新華三鷹視網(wǎng)絡(luò)

2009-03-04 06:08:00

掃描服務(wù)器人性化設(shè)計(jì)服務(wù)器應(yīng)用

2010-01-12 11:45:12

微軟Linux嵌入式開發(fā)

2025-01-16 00:20:41

2010-09-14 11:29:43

谷歌

2021-05-27 11:10:23

注冊中心業(yè)務(wù)

2015-10-14 11:29:17

數(shù)據(jù)中心細(xì)節(jié)

2011-10-19 09:27:03

數(shù)據(jù)中心照明設(shè)計(jì)

2024-11-08 13:39:49

JavaScript注冊中心語言

2013-06-05 15:56:43

華為OceanStor

2024-07-05 12:57:35

2011-12-01 14:32:13

Facebook數(shù)據(jù)中基礎(chǔ)架構(gòu)

2011-11-24 09:58:31

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 欧美一区二区三区四区视频 | 国产成人在线视频 | 国产香蕉视频在线播放 | 国产精品亚洲欧美日韩一区在线 | 麻豆久久精品 | 伊人伊人伊人 | 国产精品视频网 | 欧产日产国产精品99 | 亚洲经典一区 | 久久久久无码国产精品一区 | 日韩精品免费在线观看 | 国产精品久久久久久久午夜 | 91视频免费在观看 | www.青青草| 国产中文字幕亚洲 | 日韩精品一区二区三区四区 | 国产精华一区 | 色婷婷综合久久久中字幕精品久久 | 少妇特黄a一区二区三区88av | 国产91久久久久久久免费 | 欧美一区二区三区在线看 | 中文字幕电影在线观看 | 国产精品视频999 | 毛片一区二区 | 日韩精品亚洲专区在线观看 | 人操人免费视频 | 黄色一级毛片免费看 | 亚洲精品久久久一区二区三区 | 久久99国产精品 | 中国人pornoxxx麻豆 | 视频一区二区三区中文字幕 | 一区在线播放 | 粉色午夜视频 | 色婷婷久久 | 久久久久久国产精品 | 久久av在线播放 | 97高清国语自产拍 | 一区二区精品 | 久久国产精品免费视频 | 99精品欧美一区二区三区综合在线 | 久久久成人网 |