摘要:使用匿名內(nèi)部類是最經(jīng)典的使用方法之一??梢酝ㄟ^表達(dá)式替代匿名內(nèi)部類,簡化設(shè)計(jì)。
OO makes code understandable by encapsulating moving parting, but FP makes code understandable by minimizing moving parts. -Michael Feathers
劉光聰,程序員,敏捷教練,開源軟件愛好者,具有多年大型遺留系統(tǒng)的重構(gòu)經(jīng)驗(yàn),對(duì)OO,FP,DSL等領(lǐng)域具有濃厚的興趣。
GitHub: https://github.com/horance-liu
Email: [email protected]
Product Repository First Attempt: The Worst Implement需求1:在倉庫中查找所有顏色為紅色的產(chǎn)品
public ArrayList findAllRedProducts(ArrayList repo) { ArrayList result = new ArrayList(); for (int i=0; i指令式(Imperative)
缺乏編譯時(shí)類型安全性檢查
實(shí)現(xiàn)類型
硬編碼
重復(fù)設(shè)計(jì)
Second Attempt: Using for-eachpublic ListThird Attempt: ParameterizingfindAllRedProducts(List repo) { List result = new ArrayList<>(); for (Product p : repo) { if (p.getColor() == Color.RED) { result.add(p); } } return result; } 需求2:在倉庫中查找所有顏色為綠色的產(chǎn)品
Copy-Paste是大部分程序員最容易犯的毛病,為此引入了大量的重復(fù)代碼。
public ListfindAllGreenProducts(List repo) { List result = new ArrayList<>(); for (Product p : repo) { if (p.getColor() == Color.GREEN) { result.add(p); } } return result; } 為了消滅Hard Code和重復(fù)代碼,得到可重用的代碼,可以引入簡單的參數(shù)化設(shè)計(jì)。
public ListForth Attempt: Parameterizing with Every Attribute You Can Think OffindProductsByColor(List repo, Color color) { List result = new ArrayList<>(); for (Product p : repo) { if (p.getColor() == color) { result.add(p); } } return result; } 需求3:查找所有重量小于10的所有產(chǎn)品
大部分程序員依然會(huì)使用Copy-Paste解決這個(gè)問題,拒絕Copy-Paste的陋習(xí),最具實(shí)效的一個(gè)辦法就是把Copy-Paste的快捷鍵失效,當(dāng)每次嘗試Copy-Paste時(shí)提醒自己做更好的設(shè)計(jì)。
public ListfindProductsBelowWeight(List repo, int weight) { List result = new ArrayList<>(); for (Product p : repo) { if (p.getWeight() < weight) { result.add(p); } } return result; } 為了消除兩者重復(fù)的代碼,通過簡單的參數(shù)化往往不能完美解決這類問題,相反會(huì)引入額外的復(fù)雜度。
public ListfindProducts(List repo, Color color, int weight, boolean flag) { List result = new ArrayList<>(); for (Product p : repo) { if ((flag && p.getColor() == color) || (!flag && p.getWeight() < weight)) { result.add(p); } } return result; } 日常工作中這樣的實(shí)現(xiàn)手法非常普遍,函數(shù)的參數(shù)列表隨著需求增加不斷增加,函數(shù)邏輯承擔(dān)的職責(zé)越來越多,邏輯也變得越來越難以控制。
Firth Attempt: Abstracting over Criteria為此需要抽取出隱藏的概念,使其遍歷的算法與查找的標(biāo)準(zhǔn)能夠獨(dú)立地變化,將行為參數(shù)化。
public interface ProductSpec { boolean satisfy(Product product); }此刻findProducts的算法邏輯得到封閉。
public ListfindProducts(List repo, ProductSpec spec) { List result = new ArrayList<>(); for (Product p : repo) { if (spec.satisfy(p)) { result.add(p); } } return result; } 通過可復(fù)用的Functor來封裝各種變化,讓變化的因素控制在最小的范圍內(nèi)。
public class ColorSpec implements ProductSpec { private Color color; public ColorSpec(Color color) { this.color = color; } @Override public boolean satisfy(Product product) { return product.getColor() == color; } }public class BelowWeightSpec implements ProductSpec { private int limit; public BelowWeightSpec(int limit) { this.limit = limit; } @Override public boolean satisfy(Product product) { return product.getWeight() < limit; } }用戶的接口也變得簡單多了,而且富有表現(xiàn)力。
Listproducts = findProducts(repo, new ColorSpec(RED)); 這是經(jīng)典的OO設(shè)計(jì),如果熟悉設(shè)計(jì)模式的讀者對(duì)此已經(jīng)習(xí)以為常了。設(shè)計(jì)模式是好東西,但常常被人依葫蘆畫瓢,死板照抄,甚至被濫用。事實(shí)上,引入或去除設(shè)計(jì)模式是一個(gè)很自然的過程。與大師們交流,問究此處為何引入設(shè)計(jì)模式,得到的答案:直覺。忘記所有設(shè)計(jì)模式吧,管它是不是模式,如果設(shè)計(jì)是簡單的,它這就是模式。
至此,代碼另外還有一個(gè)明顯的壞味道,ColorSpec和BelowWeightSpec都需要繼承ProductSpec,都需要定義一個(gè)構(gòu)造函數(shù)和一個(gè)私有的字段,并重寫satisfy方法,這是一種典型的重復(fù)現(xiàn)象:重復(fù)型結(jié)構(gòu)。
因Java缺乏閉包的支持,程序員不得不承受這樣的煩惱,但此刻暫時(shí)不關(guān)心,繼續(xù)前進(jìn)。
Sixth Attempt: Composite Criteria需求4:查找所有顏色為紅色或者綠色,并且重量小于10的產(chǎn)品
按照既有的代碼結(jié)構(gòu),往往易于設(shè)計(jì)出類似ColorAndBelowWeightSpec的實(shí)現(xiàn)。
public class ColorAndBelowWeightSpec implements ProductSpec { private Color color1; private Color color2; private int limit; public ColorAndBelowWeightSpec(Color color1, Color color2, int limit) { this.color1 = color1; this.color2 = color2; this.limit = limit; } @Override public boolean satisfy(Product p) { return (p.getColor() == color1 || p.getColor() == color2) && (p.getWeight() < limit); } }存在兩個(gè)明顯的壞味道:
類名中包含And往往是違背單一職責(zé)的信號(hào)燈
ColorAndBelowWeightSpec的實(shí)現(xiàn)與ColorSpec,BelowWeightSpec之間存在明顯的重復(fù)
此刻,需要尋找更本質(zhì)的抽象來表達(dá)設(shè)計(jì),引入and/or的語義模型。
Composite Spec: AndSpec, OrSpec
Atomic Spec:ColorSpec, BeblowWeightSpec
publc class AndSpec implements ProductSpec { private Listspecs = new ArrayList<>(); public AndSpec(ProductSpec... specs) { this.specs.addAll(Arrays.asList(specs)); } @Override public boolean satisfy(Product p) { for (ProductSpec spec : specs) { if (!spec.satisfy(p)) return false; } return true; } } publc class OrSpec implements ProductSpec { private Listspecs = new ArrayList<>(); public OrSpec(ProductSpec... specs) { this.specs.addAll(Arrays.asList(specs)); } @Override public boolean satisfy(Product p) { for (ProductSpec spec : specs) { if (spec.satisfy(p)) return true; } return false; } ![clipboard.png](/img/bVtn06) } 可以通過AndSpec組合ColorSpec, BelowWeightSpec來實(shí)現(xiàn)需求,簡單漂亮,并且富有表達(dá)力。
Listproducts = findProducts(repo, new AndSpec( new OrSpec(new ColorSpec(RED), new ColorSpec(Greeen)), new BelowWeightSpec(10)); 此時(shí)設(shè)計(jì)存在兩個(gè)嚴(yán)重的壞味道:
AndSpec與OrSpec存在明顯的代碼重復(fù)
大堆的new讓人眼花繚亂
Seventh Attempt: Extract Parent先嘗試消除AndSpec與OrSpec存在的代碼重復(fù),OO設(shè)計(jì)的第一個(gè)直覺就是通過抽取基類。
class CombinableSpec implements ProductSpec { private Listspecs = new ArrayList<>(); private boolean shortcut; protected CombinableSpec(List specs, boolean shortcut) { this.specs.addAll(specs); this.shortcut = shortcut; } @Override public boolean satisfy(Product p) { for (ProductSpec spec : specs) { if (spec.satisfy(p) == shortcut) return shortcut; } return !shortcut; } } 通過參數(shù)化配置,復(fù)用CombinableSpec的實(shí)現(xiàn)。
publc class AndSpec extends CombinableSpec { public AndSpec(ProductSpec... specs) { super(Arrays.asList(specs), false); } }publc class OrSpec extends CombinableSpec { public OrSpec(ProductSpec... specs) { super(Arrays.asList(specs), true); } }如何評(píng)判boolean接口的使用呢?在不損傷可理解性的前提下,為了消除重復(fù)的設(shè)計(jì)是值得推薦的。boolean接口的可理解性關(guān)鍵依賴于調(diào)用點(diǎn)與函數(shù)接口之間的距離,如果在同一個(gè)文件,同一個(gè)類,并能在一個(gè)頁面顯示的,是完全可以接受的。
Eighth Attempt: Decorate Criteria需求5:查找所有顏色為不是紅色的產(chǎn)品
publc class NotSpec implements ProductSpec { private ProductSpec spec; public NotSpec(ProductSpec spec) { this.spec = spec; } @Override public boolean satisfy(Product p) { return !spec.satisfy(p); } }NotSpec是一種修飾了的ProductSpec,同時(shí)也使得用戶的接口也變得更加人性化了。
ListNinth Attempt: Using Static Factory to DSLproducts = findProducts(repo, new NotSpec(new ColorSpec(RED))); 之前遺留了一個(gè)問題,一大堆眼花繚亂的new使得代碼失去了部分的可讀性。
Listproducts = findProducts(repo, new AndSpec( new OrSpec(new ColorSpec(RED), new ColorSpec(Greeen)), new BelowWeightSpec(10)); 可以引入DSL改善程序的可讀性,讓代碼更具表達(dá)力。
Listproducts = findProducts(repo, and(or(color(RED), color(GREEN)), belowWeight(10))); 上述的DSL可以使用static factory的設(shè)計(jì)手段簡單實(shí)現(xiàn)。按照慣例,可以建立類似于ProductSpecs的工具類,將這些工廠方法搬遷到工具類中去。
接口與對(duì)應(yīng)工具類的對(duì)稱性設(shè)計(jì)在Java社區(qū)中應(yīng)用非常廣泛,例如標(biāo)準(zhǔn)庫中的java.util.Collection/java.util.Collections的設(shè)計(jì)。
public interface ProductSpec { boolean satisfy(Product p); }public final class ProductSpecs { public static ProductSpec color(final Color color) { return new ProductSpec() { @Override public boolean satisfy(Product p) { return p.getColor() == color; } }; } public static ProductSpec belowWeight(final int limit) { return new ProductSpec() { @Override public boolean satisfy(Product p) { return p.getWeight() < limit; } }; } public static ProductSpec and(ProductSpec... specs) { return new CombinableSpec(Arrays.asList(specs), false); } public static ProductSpec or(ProductSpec... specs) { return new CombinableSpec(Arrays.asList(specs), true); } public static ProductSpec not(final ProductSpec spec) { return new ProductSpec() { @Override public boolean satisfy(Product p) { return !spec.satisfy(p); } }; } private ProductSpecs() { throw new AssertionError("no instances"); } }此外,使用匿名內(nèi)部類,可以得到意外的驚喜。通過有限地引入閉包的概念,從而避免了類似Firth Attempt/Sixth Attempt的設(shè)計(jì)中引入多余的構(gòu)造函數(shù)和成員變量的復(fù)雜度,從而消除了部分的結(jié)構(gòu)性重復(fù)的壞味道。
當(dāng)然,要讓這些static factory可見,需要import static導(dǎo)入這些方法。
import static practical.programming.overview.ProductSpec.*; ListTenth Attempt: Moving Static Factory into Interfaceproducts = findProducts(repo, not(and(color(RED), belowWeight(10))); 使用Java8可以將這些工廠方法直接搬遷到ProductSpec的接口中去,這樣做至少得到兩個(gè)好處。
可以刪除ProductSpecs的工具類
使的接口和靜態(tài)方法(尤其靜態(tài)工廠方法)關(guān)系更加緊密
Java8并沒有因?yàn)?b>comparing等靜態(tài)工廠方法的增強(qiáng)而建立Comparators的工具類,而是直接將它們集成在Comparator的接口中,這是自Java8之后思維的一個(gè)新的轉(zhuǎn)變(Comparator.comparing的實(shí)現(xiàn)留作作業(yè)鞏固今天所學(xué)知識(shí))。
對(duì)于本例,可以將ProductSpecs刪除,將所有靜態(tài)工廠方法搬遷到ProductSpec中去。
public interface ProductSpec { boolean satisfy(Product p); static ProductSpec color(Color color) { return new ProductSpec() { @Override public boolean satisfy(Product p) { return p.getColor() == color; } }; } static ProductSpec belowWeight(int limit) { return new ProductSpec() { @Override public boolean satisfy(Product p) { return p.getWeight() < limit; } }; } static ProductSpec and(ProductSpec... specs) { return new CombinableSpec(Arrays.asList(specs), false); } static ProductSpec or(ProductSpec... specs) { return new CombinableSpec(Arrays.asList(specs), true); } static ProductSpec not(ProductSpec spec) { return new ProductSpec() { @Override public boolean satisfy(Product p) { return !spec.satisfy(p); } }; } }Eleventh Attempt: Using Null Object需求6:無條件過濾掉或不過濾查找所有產(chǎn)品
import static practical.programming.overview.ProductSpec.*; Listproducts = findProducts(repo, always(false)); public interface ProductSpec { boolean satisfy(Product p); static ProductSpec always(boolean bool) { return new ProductSpec() { @Override public boolean satisfy(Product p) { return bool; } }; } }至此,ProductSpec存在如下一些類型:
Composite Specs: and, or
Decorator Specs: not
Atomic Specs: always, color, beblowWeight
Twelfth Attempt: Using Lambda ExpressionJava8可以使用Lambda表達(dá)式改善設(shè)計(jì),增強(qiáng)表達(dá)力。
Listproducts = findProducts(repo, (Product p) -> p.getColor() == RED); 通過類型推演,可以進(jìn)一步省略Labmda表達(dá)式中參數(shù)的類型信息。
Listproducts = findProducts(repo, p -> p.getColor() == RED); 當(dāng)然,你可以通過提取static factory,構(gòu)造DSL復(fù)用這些Lambda表達(dá)式。
@FunctionalInterface public interface ProductSpec { boolean satisfy(Product p); static ProductSpec color(Color color) { return p -> p.getColor() == color; } static ProductSpec weightBelow(int limit) { return p -> p.getWeight() < limit; } }Listproducts = findProducts(repo, color(RED)); 其中,@FunctionalInterface注解標(biāo)注了ProductSpec是一個(gè)函數(shù)式接口,其抽象方法boolean satisfy(Product p)的原型描述了lambda表達(dá)式的Function Descriptor。
Thirteenth Attempt: Chaining Speciafications遺留了一個(gè)問題: 如何替換匿名內(nèi)部類,使用lambda實(shí)現(xiàn) and/or/not/always的語義?
@FunctionalInterface public interface ProductSpec { boolean satisfy(Product p); default ProductSpec negate() { return p -> !satisfy(p); } default ProductSpec and(ProductSpec other) { return (p) -> satisfy(p) && other.satisfy(p); } default ProductSpec or(ProductSpec other) { return (p) -> satisfy(p) || other.satisfy(p); } static ProductSpec always(boolean bool) { return p -> bool; } static ProductSpec color(Color color) { return p -> p.getColor() == color; } static ProductSpec belowWeight(int limit) { return p -> p.getWeight() < limit; } }這里引入了Java8一個(gè)重要的設(shè)計(jì)工具:default method,簡單漂亮,并巧妙地實(shí)現(xiàn)DSL的設(shè)計(jì),用戶接口變得更加流暢、友好。
Listproducts = findProducts(repo, color(RED).and(belowWeight(10))); Java8支持default method,擴(kuò)展了interface原來的語義,從而隱式地支持了組合式設(shè)計(jì),使的OO的設(shè)計(jì)更加完善和強(qiáng)大。
Fourteenth attempt: Using Method Reference需求7:查找所有偽劣的產(chǎn)品
Listproducts = findProducts(repo, p -> p.fake()); 可以使用Method Reference進(jìn)一步改善lambda的表達(dá)力。
ListFifteenth attempt: Abstracting over Typeproducts = findProducts(repo, Product::fake); 泛化類型信息,讓算法更具有通用性,并進(jìn)一步增強(qiáng)代碼的可復(fù)用性。
public staticList filter(List list, Predicate p) { List result = new ArrayList<>(); for (T e : list) { if (p.test(e)) { result.add(e); } } return result; } 這樣的實(shí)現(xiàn)存在一個(gè)明顯的問題:泛型參數(shù)缺乏型變的能力。通過對(duì)泛型參數(shù)實(shí)施無限定類型通配符的修飾,從而使的算法實(shí)現(xiàn)更加具有彈性和通用性。
public staticSixteenth: Maximize ReusabilityList filter(List extends T> list, Predicate super T> p) { List result = new ArrayList<>(); for (T e : list) { if (p.test(e)) { result.add(e); } } return result; } and, or, not, always在代數(shù)系統(tǒng)中具有穩(wěn)定的抽象,為此需要進(jìn)一步重構(gòu),以便最大化代碼的可復(fù)用性。這樣當(dāng)需要建立諸如NumberSpec, FruitSpec時(shí)無需重復(fù)地再寫一遍and, or, not, always的實(shí)現(xiàn)。
為此,建立更為抽象的Predicate的概念,并將通用的、抽象的negate, and, or, always搬遷到Predicate中去,使其具有更大的可復(fù)用性。
@FunctionalInterface public interface Predicate{ boolean test(T t); default Predicate negate() { return p -> !satisfy(p); } default Predicate and(Predicate super T> other) { return p -> satisfy(p) && other.satisfy(p); } default Predicate or(Predicate super T> other) { return p -> satisfy(p) || other.satisfy(p); } static Predicate always(boolean bool) { return p -> bool; } } 同時(shí),將領(lǐng)域內(nèi)的color, belowWeight等原子放回ProductSpecs工具類中去(因?yàn)椴淮嬖?b>ProductSpec的接口了),讓領(lǐng)域內(nèi)的lambda表達(dá)式具有更大的復(fù)用性。
public final class ProductSpecs { public static Predicatecolor(Color color) { return p -> p.getColor() == color; } public static Predicate belowWeight(int limit) { return p -> p.getWeight() < limit; } private ProductSpecs() { throw new AssertionError("no instances"); } } 至此,可復(fù)用的基礎(chǔ)設(shè)施便從領(lǐng)域中剝離出來,使其具有更高度的可重用性。
Seventeenth Attempt: Using Stream APIJava8可以使用集合庫的Stream復(fù)用代碼。
import static java.util.stream.Collectors.toList; repo.stream() .filter(p -> p.getColor() == RED && p.getPrice() < 10) .collect(toList());如果要支持并發(fā),則可以構(gòu)建parallelStream。
import static java.util.stream.Collectors.toList; repo.parallelStream() .filter(p -> p.getColor() == RED && p.getPrice() < 10) .collect(toList());集合類通過stream, parallelStream工廠方法創(chuàng)建Stream之后,其操作可分為2種基本類型:
Transformation:其返回值為Stream類型
Action:其返回值不是Stream類型
通過Stream的機(jī)制,實(shí)現(xiàn)了集合類的惰性求值,直至Action才真正地開始執(zhí)行計(jì)算。Transformation從某種意義上,可以看成是Stream的Builder,直至Action啟動(dòng)執(zhí)行。
Eighteenth attempt: Replace Java with ScalaScala語言是一門跨越OO和FP的一個(gè)混血兒,可以方便地與Java進(jìn)行互操作。在Scala中,函數(shù)作為一等公民,使用Lambda是一個(gè)很自然的過程。當(dāng)你熟悉了Scala,我相信你絕對(duì)會(huì)放棄Java,放棄Java8,猶如作者本人一樣。
repo.filter(p => p.color == RED && p.weight < 10)遺留了三個(gè)問題:
如何復(fù)用lambda表達(dá)式?
如何實(shí)現(xiàn) and/or/not的語義?
如何實(shí)現(xiàn) always的語義?
Nineteenth Attempt: Abstracting Control Structure引入靜態(tài)工廠方法及其操作符重載的機(jī)制構(gòu)造內(nèi)部DSL。
import ProductSpec._ repo.filter(color(RED) && belowWeight(10))object ProductSpec { def color(color: Color) = ??? def bebowWeight(limit: Int) = ??? }如何替換實(shí)現(xiàn)???,并讓其具有&&, ||, !的語義呢?
object ProductSpec { def color(color: Color) = new Predicate((p: Product) => p.color == color) def bebowWeight(limit: Int) = new Predicate((p: Product) => p.weight < limit) }Predicate一個(gè)擴(kuò)展匿名函數(shù)A => Boolean的子類,其中,從面向?qū)ο蟮慕嵌瓤矗?b>A => Boolean的類型為Function[A, Boolean]。
class Predicate[A](pred: A => Boolean) extends (A => Boolean) { override def apply(a: A) = pred(a) def &&(that: A => Boolean) = new Predicate[A](x => pred(x) && that(x)) def ||(that: A => Boolean) = new Predicate[A](x => pred(x) || that(x)) def unary_! = new Predicate[A](x => !pred(x)) }其中!是一個(gè)一元操作符。
Twentieth Attempt: Using Companion Objectalways靜態(tài)工廠方法,可以搬遷到Predicate的伴生對(duì)象中去。
object Predicate { def always[A](bool: Boolean) = new Predicate[A](_ => bool) }Predicate的設(shè)計(jì)既使用了OO的特性,又引入了FP的思維,Scala使其兩者如此和諧、完美,簡直不可思議。
Conclusion世界是多樣性的,計(jì)算機(jī)工業(yè)也不僅僅只存在一種方法論。在我的哲學(xué)觀里,OO和FP之間并不矛盾,而是一個(gè)和諧的,相互補(bǔ)充的統(tǒng)一體。
除了C++語言之外,使得我最偏愛Scala,多范式,一個(gè)問題存在多種解決方案等等思維習(xí)慣,給了程序員最靈活、最自由的空間。
Review Comprator.comparing以標(biāo)準(zhǔn)庫Collections.sort,及其Comparator在Java8中的增強(qiáng),及其Comparator.comparing的泛型定義復(fù)習(xí)今天所學(xué)知識(shí)。
public final class Collectins { private Collectins() { } public staticvoid sort(List list, Comparator super T> c) { list.sort(c); } } 使用匿名內(nèi)部類是Collectins.sort最經(jīng)典的使用方法之一。
Collections.sort(products, new Comparator() { @Override public int compare(Product p1, Product p2) { return p1.getName().compareTo(p2.getName); } });可以通過lambda表達(dá)式替代匿名內(nèi)部類,簡化設(shè)計(jì)。
Collections.sort(products, (Product p1, Product p2) -> p1.getName().compareTo(p2.getName));通過類型推演,但依然得到編譯器類型安全的保護(hù)。
Collections.sort(products, (p1, p2) -> p1.getName().compareTo(p2.getName));通過Comprator.compring的靜態(tài)工廠方法,改善表達(dá)力。
Collections.sort(persons, comparing(p -> p.getName()))通過Function Reference的機(jī)制,進(jìn)一步改善表達(dá)力。
Collections.sort(persons, comparing(Person::getName()))其中,Comprator.compring的實(shí)現(xiàn)為:
@FunctionalInterface public interface Comparator{ int compare(T o1, T o2); static > Comparator comparing(Function super T, ? extends U> extractor) { return (c1, c2) -> extractor.apply(c1).compareTo(extractor.apply(c2)); } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65592.html
摘要:函數(shù)式編程,一看這個(gè)詞,簡直就是學(xué)院派的典范。所以這期周刊,我們就重點(diǎn)引入的函數(shù)式編程,淺入淺出,一窺函數(shù)式編程的思想,可能讓你對(duì)編程語言的理解更加融會(huì)貫通一些。但從根本上來說,函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進(jìn)行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數(shù)式編程(Functional Programming),一...
摘要:不過因?yàn)楦鱾€(gè)平臺(tái)互相挖人的關(guān)系,導(dǎo)致關(guān)注的一些主播分散到了各個(gè)直播平臺(tái),來回切換有點(diǎn)麻煩,所以萌生了做一個(gè)視頻聚合站的想法。后續(xù)我們會(huì)對(duì)這三個(gè)部分的功能做逐一展開說明。正則處理要求比較高,但是幾乎能應(yīng)對(duì)所有的情況,屬于大殺器。 前言 作為一個(gè)爐石傳說玩家,經(jīng)常有事沒事開著直播網(wǎng)站看看大神們的精彩表演。不過因?yàn)楦鱾€(gè)平臺(tái)互相挖人的關(guān)系,導(dǎo)致關(guān)注的一些主播分散到了各個(gè)直播平臺(tái),來回切換有點(diǎn)麻...
摘要:例日本的企業(yè)由于種種原因把制作放在本土,這樣產(chǎn)品需要遠(yuǎn)洋運(yùn)輸?shù)娇蛻羰种?。讓生產(chǎn)過程流動(dòng)起來,減少部門之間的浪費(fèi)。盡善盡美快速反饋,積極改進(jìn)。 精益思想Lean thinking 浪費(fèi)專指消耗了資源而不創(chuàng)造價(jià)值的一切人類活動(dòng) 需要糾正的錯(cuò)誤 生產(chǎn)了無需求的產(chǎn)品 由此造成的庫存和積壓 不必要的工序 員工的盲目走動(dòng) 貨物從一地到另一地的盲目搬運(yùn) 由于上道工序發(fā)送傳遞不及時(shí),使做下一道工序的...
摘要:關(guān)于中順序的基本操作關(guān)于中順序的基本操作寫在前面最近研究一下中的順序,在網(wǎng)絡(luò)上找了一會(huì)兒,發(fā)現(xiàn)少有詳細(xì)的介紹,顧此在此處說說順序,才學(xué)疏淺,如有不對(duì),望賜教。上述代碼中標(biāo)記位置中,返回下一次操作時(shí)的位置。關(guān)于JAVA中順序IO的基本操作 寫在前面 最近研究一下JAVA中的順序IO,在網(wǎng)絡(luò)上找了一會(huì)兒,發(fā)現(xiàn)少有詳細(xì)的介紹,顧此在此處說說順序IO,才學(xué)疏淺,如有不對(duì),望賜...
書籍完整目錄 4.1 react 代碼規(guī)范 showImg(https://segmentfault.com/img/bVyE9m); 關(guān)于 基礎(chǔ)規(guī)范 組件結(jié)構(gòu) 命名規(guī)范 jsx 書寫規(guī)范 eslint-plugin-react 關(guān)于 在代碼的設(shè)計(jì)上,每個(gè)團(tuán)隊(duì)可能都有一定的代碼規(guī)范和模式,好的代碼規(guī)范能夠提高代碼的可讀性便于協(xié)作溝通,好的模式能夠上層設(shè)計(jì)上避免不必要的 bug 出現(xiàn)。本節(jié)會(huì)參考...
閱讀 1253·2021-11-22 13:54
閱讀 1440·2021-11-22 09:34
閱讀 2717·2021-11-22 09:34
閱讀 4031·2021-10-13 09:39
閱讀 3352·2019-08-26 11:52
閱讀 3373·2019-08-26 11:50
閱讀 1541·2019-08-26 10:56
閱讀 1923·2019-08-26 10:44