Als ich Anfang April in Peking war, sagte Klassenkamerad Xu Hao, dass die von Kollegen in unserem Unternehmen geschriebenen Artikel zu einfach seien und zu viel Wert auf Details legten. Dann nahm ich die Sesamkörner und die Wassermelone verloren, also habe ich den Blog nicht erneut aktualisiert (eigentlich liegt die Ursache darin, dass das Projekt zu beschäftigt ist). Letzte Woche habe ich mit mehreren anderen Kollegen an der Veranstaltung „Martin Fowler Shenzhen Tour“ teilgenommen. Mein Kollege Tashi und ich haben zusammen mit Yang Yun (in Jianghu als „Big Devil“ bekannt) eine „FullStack Language JavaScript“ beigesteuert ) )s Thema ist „Beherrschung der funktionalen Programmierung und Steuerung der Systemkomplexität“, und das Thema von Li Xin (in Jianghu als Xin Ye bekannt) ist „Parallelität: Vergangenheit und Leben nach dem Tod“.
Während ich mit anderen Kollegen probte, stellte ich plötzlich fest, dass unsere Themen mehr oder weniger zusammenhingen. Der Teil, über den ich gesprochen habe, betraf auch ereignisbasierte Parallelitätsmechanismen und funktionale Programmierung. Wenn Sie sorgfältig darüber nachdenken, sollte es mit den Eigenschaften von JavaScript selbst zusammenhängen:
Event-Based Node.js ist ein sehr typisches Modell für Parallelität
Funktionale Programmierung sorgt dafür, dass Rückrufe auf natürliche Weise unterstützt werden, wodurch es sich sehr gut für asynchrone/Ereignismechanismen eignet.
Funktionale Programmierfunktionen machen es sehr gut zum Schreiben geeignet DSL
Am Tag nach dem Treffen wollte ich plötzlich ein Aggregationsmodell mithilfe funktionaler Programmierung im Projektcode neu schreiben. Es stellte sich heraus, dass die Idee vage verwandt war zu NoSQL und stellte außerdem fest, dass ich viele Mängel hatte.
Das folgende Beispiel stammt aus einer Szene in einem tatsächlichen Projekt, aber die Domäne wurde geändert, was jedoch keinerlei Auswirkungen auf das Lesen und Verstehen des Mechanismus dahinter hat.
Stellen Sie sich eine Anwendung vor, in der Benutzer eine Liste der abonnierten RSS sehen können. Jedes Element in der Liste (Feed genannt) enthält ein id
, einen Artikeltitel title
und einen Link zum Artikel url
.
Das Datenmodell sieht so aus:
var feeds = [ { 'id': 1, 'url': 'http://abruzzi.github.com/2015/03/list-comprehension-in-python/', 'title': 'Python中的 list comprehension 以及 generator' }, { 'id': 2, 'url': 'http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/', 'title': '使用inotify/fswatch构建自动监控脚本' }, { 'id': 3, 'url': 'http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/', 'title': '使用underscore.js构建前端应用' } ];
Das Modell ist sehr einfach, wenn diese einfache Anwendung keine benutzerbezogenen Informationen hat. Doch bald musste die Anwendung von der Standalone-Version auf die Webversion erweitert werden, das heißt, wir führten das Konzept der Benutzer ein. Jeder Benutzer kann eine solche Liste sehen. Darüber hinaus können Benutzer auch Feeds sammeln. Natürlich können Benutzer nach dem Sammeln auch die Liste der gesammelten Feeds einsehen.
Da jeder Benutzer mehrere Feeds sammeln kann und jeder Feed auch von mehreren Benutzern gesammelt werden kann, ist die Viele-zu-Viele-Beziehung zwischen ihnen wie in der Abbildung oben dargestellt . Sie denken vielleicht auch an Dinge wie:
$ curl http://www.php.cn/:9999/user/1/feeds
, um alle 1
Benutzer feed
usw. abzurufen, aber diese sind nicht wichtig. Das eigentliche Problem besteht darin, dass, nachdem Sie alle Feeds erhalten haben, In der Benutzeroberfläche oben müssen Sie jedem Feed ein -Attribut makred
hinzufügen. Dieses Attribut wird verwendet, um anzugeben, ob der Feed erfasst wurde. Entsprechend der Schnittstelle kann es sich um einen gelben Stern oder ein rotes Herz handeln.
Aufgrund der Einschränkungen relationaler Datenbanken müssen Sie eine Aggregation auf der Serverseite durchführen B. Feeding. Wickeln Sie das -Objekt ein, um ein Objekt wie FeedWrapper
zu generieren:
public class FeedWrapper { private Feed feed; private boolean marked; public boolean isMarked() { return marked; } public void setMarked(boolean marked) { this.marked = marked; } public FeedWrapper(Feed feed, boolean marked) { this.feed = feed; this.marked = marked; } }
und definieren Sie dann ein Dienstobjekt wie FeedService
:
public ArrayList<FeedWrapper> wrapFeed(List<Feed> markedFeeds, List<Feed> feeds) { return newArrayList(transform(feeds, new Function<Feed, FeedWrapper>() { @Override public FeedWrapper apply(Feed feed) { if (markedFeeds.contains(feed)) { return new FeedWrapper(feed, true); } else { return new FeedWrapper(feed, false); } } })); }
Okay, dies kann als passable Implementierung betrachtet werden, aber statischesstark typisiertes Java ist etwas zurückhaltend, dies zu tun, und sobald neue Änderungen auftreten (was mit ziemlicher Sicherheit passieren wird), müssen wir diesen Teil noch einfügen die Logik Fügen Sie es in JavaScript ein und sehen Sie, wie es diesen Prozess vereinfacht.
Kommen wir zum Thema. In diesem Artikel verwenden wir lodash
als funktionale Programmierbibliothek, um das Schreiben von Code zu vereinfachen. Da es sich bei JavaScript um eine dynamisch schwach typisierte Sprache handelt, können wir einem Objekt jederzeit Attribute hinzufügen. Auf diese Weise kann eine einfache map
-Operation den obigen Java-Code vervollständigen:
_.map(feeds, function(item) { return _.extend(item, {marked: isMarked(item.id)}); });
wobei die Funktion isMarked
wird so etwas tun:
var userMarkedIds = [1, 2]; function isMarked(id) { return _.includes(userMarkedIds, id); }
Das heißt, es wird überprüft, ob die eingehenden Parameter in einer Liste enthalten sind userMarkedIds
Diese Liste kann durch die folgende Anfrage abgerufen werden:
$ curl http://www.php.cn/:9999/user/1/marked-feed-ids
Der Zweck, nur die ID zu erhalten, besteht darin, die Datengröße der Netzwerkübertragung zu reduzieren. Natürlich können Sie auch alle /marked-feeds
anfordern und dann _.pluck(feeds, 'id')
lokal ausführen, um alle id
-Attribute zu extrahieren.
嗯,代码是精简了许多。但是如果仅仅能做到这一步的话,也没有多大的好处嘛。现在需求又有了变化,我们需要在另一个页面上展示当前用户的收藏夹(用以展示用户所有收藏的feed)。作为程序员,我们可不愿意重新写一套界面,如果能复用同一套逻辑当然最好了。
比如对于上面这个列表,我们已经有了对应的模板:
{{#each feeds}} <li class="list-item"> <p class="section" data-feed-id="{{this.id}}"> {{#if this.marked}} <span class="marked icon-favorite"></span> {{else}} <span class="unmarked icon-favorite"></span> {{/if}} <a href="/feeds/{{this.url}}"> <p class="detail"> <h3>{{this.title}}</h3> </p> </a> </p> </li> {{/each}}
事实上,这段代码在收藏夹页面上完全可以复用,我们只需要把所有的marked
属性都设置为true就行了!简单,很快我们就可以写出对应的代码:
_.map(feeds, function(item) { return _.extend(item, {marked: true}); });
漂亮!而且重要的是,它还可以如正常工作!但是作为程序员,你很快就发现了两处代码的相似性:
_.map(feeds, function(item) { return _.extend(item, {marked: isMarked(item.id)}); }); _.map(feeds, function(item) { return _.extend(item, {marked: true}); });
消除重复是一个有追求的程序员的基本素养,不过要消除这两处貌似有点困难:位于marked:
后边的,一个是函数调用,另一个是值!如果要简化,我们不得不做一个匿名函数,然后以回调的方式来简化:
function wrapFeeds(feeds, predicate) { return _.map(feeds, function(item) { return _.extend(item, {marked: predicate(item.id)}); }); }
对于feed列表,我们要调用:
wrapFeeds(feeds, isMarked);
而对于收藏夹,则需要传入一个匿名函数:
wrapFeeds(feeds, function(item) {return true});
在lodash
中,这样的匿名函数可以用_.wrap
来简化:
wrapFeeds(feeds, _.wrap(true));
好了,目前来看,简化的还不错,代码缩减了,而且也好读了一些(当然前提是你已经熟悉了函数式编程的读法)。
如果仔细审视isMarked
函数,会发现它对外部的依赖不是很漂亮(而且这个外部依赖是从网络异步请求来的),也就是说,我们需要在请求到markedIds
的地方才能定义isMarked
函数,这样就把函数定义绑定
到了一个固定的地方,如果该函数的逻辑比较复杂,那么势必会影响代码的可维护性(或者更糟糕的是,多出维护)。
要将这部分代码隔离出去,我们需要将ids
作为参数传递出去,并得到一个可以当做谓词(判断一个id是否在列表中的谓词)的函数。
简而言之,我们需要:
var predicate = createFunc(ids); wrapFeeds(feeds, predicate);
这里的createFunc
函数接受一个列表作为参数,并返回了一个谓词函数。而这个谓词函数就是上边说的isMarked
。这个神奇的过程被称为柯里化currying
,或者偏函数partial
。在lodash
中,这个很容易实现:
function isMarkedIn(ids) { return _.partial(_.includes, ids); }
这个函数会将ids
保存起来,当被调用时,它会被展开为:_.includes(ids, <id>)
。只不过这个<id>
会在实际迭代的时候才传入:
$('/marked-feed-ids').done(function(ids) { var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids)); console.log(wrappedFeeds); });
这样我们的代码就被简化成了:
$('/marked-feed-ids').done(function(ids) { var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids)); var markedFeeds = wrapFeeds(feeds, _.wrap(true)); allFeedList.html(template({feeds: wrappedFeeds})); markedFeedList.html(template({feeds: markedFeeds})); });
Das obige ist der detaillierte Inhalt vonEine kurze Diskussion zum JavaScript-Tutorial zur funktionalen Programmierung (Bild). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!