惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
LangChain Blog
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
博客园_首页
T
Tailwind CSS Blog
P
Proofpoint News Feed
雷峰网
雷峰网
D
Darknet – Hacking Tools, Hacker News & Cyber Security
IT之家
IT之家
V
Vulnerabilities – Threatpost
阮一峰的网络日志
阮一峰的网络日志
C
CERT Recently Published Vulnerability Notes
Attack and Defense Labs
Attack and Defense Labs
S
Schneier on Security
Security Archives - TechRepublic
Security Archives - TechRepublic
L
Lohrmann on Cybersecurity
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
The Register - Security
The Register - Security
N
Netflix TechBlog - Medium
Hugging Face - Blog
Hugging Face - Blog
Project Zero
Project Zero
博客园 - 叶小钗
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
Latest news
Latest news
S
SegmentFault 最新的问题
C
Cyber Attacks, Cyber Crime and Cyber Security
Google Online Security Blog
Google Online Security Blog
Recorded Future
Recorded Future
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
L
LINUX DO - 最新话题
Google DeepMind News
Google DeepMind News
P
Privacy International News Feed
I
InfoQ
F
Fortinet All Blogs
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Threatpost
T
Tenable Blog
B
Blog RSS Feed

Minko Gechev's blog

skillgrade Unit Tests for AI Agent Skills You Should Care About AI Generative Development LLM-first Web Framework Reactive framework in ~200 lines of JavaScript Managing Angular Are LLMs going to replace us? Prefetching Heuristics Design Patterns in Open Source Projects - Part II What I learned doing 125 public talks - Part I Dynamic imports solve all the problems, right? 5 Angular CLI Features You Didn't Know About Angular quicklink Preloading Strategy Introducing Bazel Schematics for Angular CLI Building TypeScript Projects with Bazel Joining Google Playing Mortal Kombat with TensorFlow.js. Transfer learning and data augmentation Fast, extensible, configurable, and beautiful linter for Go Introducing Guess.js - a toolkit for enabling data-driven user-experiences on the Web Machine Learning-Driven Bundling. The Future of JavaScript Tooling. JavaScript Decorators for Declarative and Readable Code 3 Tricks For Using Redux and Immutable.js with TypeScript Follow Your Dream Career with Open Source. Personal Story. Redux Anti-Patterns - Part 1. State Management. Faster Angular Applications - Understanding Differs. Developing a Custom IterableDiffer Faster Angular Applications - Part 2. Pure Pipes, Pure Functions and Memoization Faster Angular Applications - Part 1. On Push Change Detection and Immutability Understanding Dynamic Scoping and TemplateRef Implementing a Simple Compiler on 25 Lines of JavaScript Developing Statically Typed Programming Language WebVR for a Gamified IDE 7 Angular Tools That You Should Consider Announcing ngrev - Reverse Engineering Tool for Angular Implementing Angular's Dependency Injection in React. Understanding Element Injectors. Distributing an Angular Library - The Brief Guide Angular in Production Ahead-of-Time Compilation in Angular 2.5X Smaller Angular 2 Applications with Google Closure Compiler Using Stripe with Angular (Deprecated) Building an Angular Application for Production Implementing the Missing "resolve" Feature of the Angular 2 Router Scalable Single-Page Application Architecture Managing ambient type definitions and dealing with the "Duplicate identifier" TypeScript error Static Code Analysis of Angular 2 and TypeScript Projects Enforcing Best Practices with Static Code Analysis of Angular 2 Projects ViewChildren and ContentChildren in Angular Dynamically Configuring the Angular's Router Angular 2 Hot Loader Lazy Loading of Route Components in Angular 2 Aspect-Oriented Programming in JavaScript Flux in Depth. Store and Network Communication. Using JSX with TypeScript Flux in Depth. Overview and Components. Even Faster AngularJS Data Structures Boost the Performance of an AngularJS Application Using Immutable Data - Part 2 Angular2 - First Impressions Build Your own Simplified AngularJS in 200 Lines of JavaScript Persistent State of ReactJS Component Boost the Performance of an AngularJS Application Using Immutable Data Processing Binary Protocols with Client-Side JavaScript Stream your Desktop to HTML5 Video Element Multi-User Video Conference with WebRTC Asynchronous calls with ES6 generators Binary Tree iterator with ES6 generators WebRTC chat with React.js AngularJS in Patterns (Part 3) AngularJS in Patterns (Part 2). Services. Using GitHub Pages with Jekyll! AngularJS in Patterns (Part 1). Overview of AngularJS Singleton in JavaScript Express over HTTPS What I get from the JavaScript MV* frameworks Remote Desktop Client with AngularJS and Yeoman The magic of $resource (or simply a client-side Active Record) AngularJS Inheritance Patterns AngularAOP v0.1.0 Advanced JavaScript at Sofia University AngularJS style guide Lazy prefetching of AngularJS partials VNC client on 200 lines of JavaScript Aspect-Oriented Programming with AngularJS CSS3 flipping effect Practical programming with JavaScript Why I should use publish/subscribe in JavaScript JavaScript, the weird parts Functional programming with JavaScript plainvm Looking for performance? Probably you should NOT use [].sort (V8) JavaScript image scaling ELang Caching CSS with localStorage Self-invoking functions in JavaScript (or Immediately Invoked Function Expressions) Asus N56VZ + Ubuntu 12.04 (en) Asus N56VZ + Ubuntu 12.04 Debian Squeeze + LXDE on Google Nexus S (or having some fun while suffering) HTML5 image editor Курсови проекти – ФМИ Carousel Gallery SofiaJS...
Design Patterns in Open Source Projects - Part I
2021-01-18 · via Minko Gechev's blog

Edit · Jan 18, 2021 · 6 minutes read · Follow @mgechev Design patterns Open-source Software engineering

I see two common frustrations around design patterns:

  1. It’s easy to memorize patterns but hard to figure out where and how to use them
  2. OOP is lame, and design patterns suck

If the second bullet point resonates with you, this blog post is probably not a good use of your time.

Now let’s focus on the first point. It’s not easy to understand design patterns just by reading the gang of four book or dummy examples on Wikipedia. That’s why I want to share a couple of real-world applications in open source projects that you can see and run yourself.

I plan to start the series with content for how ngrev uses State, Composite, and Memento. Today we’re going to focus on the State pattern.

Project Overview

ngrev is an open-source application for reverse engineering of Angular apps. I developed it originally for my talk “Mad Science with the Angular Compiler” for ng-conf 2017, but kept maintaining it over the years. Later on, the Angular team released Ivy and introduced a brand new compiler, which broke the parsing infrastructure of ngrev. Recently, however, François and Viktor helped me to revive the project.

To get a better idea of the features of ngrev, I’d recommend you to have a look at this short video:

High-Level Architecture

ngrev is an Electron app, so it has its main process, referenced as User Interface (UI) on the diagram below, and Render Process. Additionally, the Render Process also starts a Background Process used for heavy computations to keep the Render Process responsive to the UI queries.

All the communication within the processes of the application is happening via IPC (Inter-Process Communication) via message passing.

Implementation Overview

Let us look at how the app works, to get a better idea of where the state pattern fits in the picture.

When you open ngrev, the UI sends a message to the Render Process to return the current theme. After that the UI applies it and allows the user to select a project. In the meantime, the Render Process creates the Background child Process and establishes a communication channel.

Once the user selects a project, the UI sends a message to the Render Process, which forwards it to the Background Process. The Background Process uses ngast, a wrapper around the Angular compiler, to parse the specified application and return the project symbols to the Render Process. From there, the Render Process returns them to the UI, which shows them as a graph.

It gets interesting when folks navigate from one node to another - for example, when the user double clicks on a module to explore its declaration graph. Such action will trigger a message to the Render Process, which will talk to the stateful Background Process. The Background Process will use the Angular incremental compiler to parse the module’s declaration graph and return it to the UI. Finally, the UI will transition to the new view.

Here comes the first design pattern we’ll talk about - the State pattern.

The State Pattern

The state pattern is a behavioral software design pattern that allows an object to alter its behavior when its internal state changes. This pattern is close to the concept of finite-state machines. The state pattern can be interpreted as a strategy pattern, which is able to switch a strategy through invocations of methods defined in the pattern’s interface.

The Background Process represents each view with an instance of a concrete implementation of the State class:

export abstract class State {
  abstract getData(): VisualizationConfig<any>;
  abstract getMetadata(id: string): Metadata | null;
  abstract nextState(id: string): State | null;
  destroy(): void {}
}

The State’s interface allows you to:

  • Get data for a particular view (getData). This method will return all the nodes and their relationships. The VisualizationConfig has a list of nodes and a list of edges, where each edge contains the nodes it connects and the direction of their relationship
  • Get metadata for a particular symbol. For example, if the state object represents a template view, the getMetadata will return metadata for the specified node of the template (its attributes, associated directives, and references)
  • Transition to a new state. That’s the essence of the state pattern - it implements a state machine, so you can transition from one state to another. The method accepts a symbol ID. The ID could be the identifier of any component, directive, etc., which has a node associated with it in the current state object. After that, the nextState method will check the identifier’s type and perform the state transition
  • Finally, we can also destroy a state. Imagine the state object allocated memory or set event listeners. The destroy method allows us to perform a cleanup

Using States

Let us now look at how ngrev uses states! On the UML class diagram above, the consumer of the state object is the Context class. In ngrev the context is the Background Process.

If we look into the Background Process’ implementation we’ll find where it responds to state transition messages:

this.parentProcess.on(Message.DirectStateTransition, (data: DirectStateTransitionRequest, responder: Responder) => {
  const nextState: State | null = lastState.nextState(data.id);
  if (nextState) {
    this.states.push(nextState);
    responder({
      topic: Message.DirectStateTransition,
      available: true
    });
    return;
  }
  responder({
    topic: Message.DirectStateTransition,
    available: false
  });
});

This is a bit simplified implementation, but it should give us a good idea how to perform state transitions. First, we get the current state (lastState) and after that invoke its nextState method, passing the symbol we want to navigate to. As an example, lastState could be representing the module declarations view and the data.id could be an identifier of a component symbol. When we call nextState passing this identifier, we’ll get a new ComponentState object.

Let us track the instantiation of the next state! For the purpose, let us explore the nextState implementation of the TemplateState class:

nextState(nodeId: string): State | null {
  if (nodeId === this.symbolId) {
    return null;
  }
  const data = this.symbols[nodeId].data;
  if (!data) {
    return null;
  }
  if (data.symbol instanceof DirectiveSymbol || data.symbol instanceof ComponentSymbol) {
    return new DirectiveState(this.context, data.symbol);
  } else if (data.symbol instanceof InjectableSymbol) {
    return new ProviderState(this.context, data.symbol);
  } else if (data.symbol instanceof PipeSymbol) {
    return new PipeState(this.context, data.symbol);
  }
  return null;
}

We first check if we’re trying to navigate to the same symbol or a non-existing one. If we hit any of these branches, we directly return null without performing a state transition.

If the user has selected a directive or a component we navigate to the DirectiveState. ngrev uses the DirectiveState class for both components and directives because they have a lot of shared logic. We do the same for providers and pipes respectively.

Use Cases

Generally, when you notice a patterns in your app that remind you of a state machine, there might be a good use case for the state design pattern.

Over the years, I found the state pattern to be a very convenient way for managing stateful transitions. Often we have different data visualizations that share similar interfaces or shapes, and we can navigate from one such visualization to another. The state pattern is a clean way to manage this process.

It’s important to not force using state if there’s a more simple solution. Especially when first learning object-oriented design, developers often try to fit patterns everywhere. This mindset leads to over engineered solutions which don’t scale well nor respond to changing business requirements.

What’s Next?

As the next step I’d recommend you to clone ngrev and try to make sense of everything we talked about.

Reading about code is an important part of the story, but practice is where you find how all ideas fit together.