Thursday, March 24, 2022

[Architecture] 控制反轉 (Inversion of Control) vs 依賴反轉 (Dependency Inversion)

控制反轉 (Inversion of Control) vs 依賴反轉 (Dependency Inversion)

兩者不相等!


依賴倒置原則 (Dependency Inversion Principle, DIP) :

高階模組不應該依賴於低階模組,兩者都該依賴抽象。

抽象不應該依賴於具體實作方式。

具體實作方式則應該依賴抽象。


舉例來說,此程式違反了 依賴倒置原則, 它使高階模組 (Computer) 依賴 低階模組 (英雄聯盟):

====================================================================

class Computer {

    // 依賴於低階模組:『具體』的英雄聯盟,而非 『抽象』的遊戲

    private 英雄聯盟 lol;

    public Computer() {

        // 預設安裝遊戲: 英雄聯盟

        lol = new 英雄聯盟();

    }

    public Computer(英雄聯盟 lol) {

        this.lol = lol;

    }

    public void playGame() {

        if (lol != null)

            lol.play();

    }

}

class 英雄聯盟 {


    public void play() {

        System.out.print("德瑪西雅~");

    }

}

====================================================================

然而,他還是能透過 IoC/DI ,

被動取得 類別 “英雄聯盟” 的實例 lol:

解除了 高階模組 (Computer) 主動對 低階元件 (英雄聯盟) 的實例方式,

卻 解除不了 高階模組 對 低階模組 的 依賴關係。


因為 高階模組 依賴的是 具體實作 (英雄聯盟),

而非 抽象 (介面 or 抽象類別)。


如果想實現 依賴倒置原則 ?

還記得 依賴倒置原則 (Dependency Inversion Principle, DIP) 的結尾嗎?

僅僅將『 高階模組的依賴對象,由具體改為抽象 』,是 不夠 的,

因為高階模組 欲使用 低階模組的物件時,還是 需要自己 new 具體實作類別。


高階模組,依賴於抽象,而非低階模組。

但 要使用 該抽象的 具體產品 (低階模組) 時,


不用也不需要知道是哪種 具體產品

不再自己實例 具體產品,而是 服務容器 會提供給他 。


Reference:

https://notfalse.net/3/ioc-di


[Architecture] IoC/DI

IoC,是一種 設計原則:

藉由 『分離組件 (Components) 的設置與使用』,來降低類別或模組之間的耦合度 (i.e., 解耦)。

Martin Fowler,因認為 “IoC” 的意義易使人困惑,

於 2000 年初,與多位 IoC 提倡者,給予其實作方式一個更具體的名稱

— — "Dependency Injection (依賴注入)"。

由 服務容器 (IoC 容器) 透過 “依賴注入”,給予 高階模組 所需得具體產品:


取代傳統的主動建立實例:




IoC/DI 很好的實現 好萊塢原則 (Hollywood Principle)、

依賴倒置原則 (Dependency Inversion Principle, DIP) 、 開閉原則 (Open-Closed Principle) …etc.,

是框架的必備特徵,當然也是各語言主流框架的核心 (e.g. : Spring, Laravel, .Net MVC …) 。


控制流程有 Framework 完成


callback function as a dependency of the object that it is being passed into. 

DI is the process of providing the callback (the dependency) to the object. (For example: by giving it to the object via its constructor, a method call, a setter, etc.).


翻译: callback是具体的依赖, DI是注入依赖的过程


DI是IoC的子集





IoC(I nversion o f C ontrol ): - 这是一个通用术语,以多种方式实现(事件,代理等)。

DI(D ependency I njection): - DI是IoC的子類型,通過構造函數註入,setter註入或接口註入實現


依賴注入 (Dependency Injection)


有以下三種形式:

  1. 建構元注入 (Constructor Injection)
  2. 設值方法注入 (Setter Injection)
  3. 介面注入 (Interface Injection)


class Computer implements GameInjector {

    private Game game; // 依賴 『抽象』,而非『具體』

    // 建構元注入 (Constructor Injection)
    public Computer(Game game) {
        this.game = game;
    }

    // 設值方法注入 (Setter Injection)
    public void setGame(Game game) {
        this.game = game;
    }

    // 介面注入 (Interface Injection)
    @Override
    public void injectGame(Game game) {
        this.game = game;
    }

    public void playGame() {
        if (game != null) {
            game.play();
        }
    }
}

// 遊戲注入者
// 可以規範: 任何需要 "遊戲" 的模組 都必須實做此介面
interface GameInjector{
    void injectGame(Game game);
}



可以看到程式中,沒有任何 “具體實作類別” 的名稱,
而是由 依賴注入 取得 插件實例,
高階模組,完全沒有與具體實作 耦合,
實現了 依賴倒置原則 (Dependency Inversion Principle, DIP) !


Reference:

https://www.codeproject.com/Articles/592372/Dependency-Injection-DI-vs-Inversion-of-Control-IO

https://stackoverflow.com/questions/6550700/inversion-of-control-vs-dependency-injection

https://martinfowler.com/articles/dipInTheWild.html#YouMeanDependencyInversionRight

[Architecture] Abstract VS Interface 差異

類別實作Interface使用關鍵字implements:類別繼承Abstract Class使用關鍵字extends。

// 類別實作Interface

public class ConcreteClass implements InterfaceA { ... }

// 類別繼承Abstract Class

public class ConcreteClass extends AbstractClassC { ... }


Interface只能繼承(extends)Interface;Abstract Class能繼承類別也能實作Interface。

// Interface只能繼承Interface

public interface InterfaceA extends InterfaceB { ... }

// Abstract Class能繼承類別及實作Interface

public abstract class AbstractClassC extends AbstractClassD implements InterfaceC { ... }


類別能實作多個Interface,Interface能繼承多個Interface;類別只能繼承一個Abstract Class。

// Interface能實作多個Intetface

public interface InterfaceA extends InterfaceB, InterfaceC { ... }

// 類別能實作多個介面

public class ConcreteClass implements InterfaceA, InterfaceB { ... }

// 類別只能繼承一個Abstract Class

public class ConcreteClass extends AbstractClassC { ... }


Interface不可有實作方法(除了static method及Java 8的default method);Abstract Class可以有實作方法,也可以有無實作的抽象方法。

public interface InterfaceA {

    void doSomething(); // 方法無實作
    
    // 靜態方法有實作
    static String getSomething() {
        return "Something";
    }
    
    // Java 8 default methods
    default void doSomeStuff() { 
        System.out.println("do some stuff");
    }
    
}
public abstract class AbstractClassC {
    
    void doSomething() {
        System.out.println("do something"); // 方法有實作
    }

    abstract void doStuff(); // 無實作的抽象方法
    
}

Interface中的變數預設(也只能是)public final static;Abstract Class可自訂變數的存取範圍。

public interface InterfaceA {

    int VALUE = 1; // implicit public static final 

}
public abstract class AbstractClassC {
    
    private int value; // 自訂存取範圍

}

Interface的方法預設(也只能是)public;Abstract Class則可自訂方法的存取範圍(但抽像方法不可為private)。

public interface InterfaceA {

    void doSomething(); // implicit public

}
public abstract class AbstractClassC {
    
    // 自訂存取範圍
    private void doSomething() { 
        System.out.println("do something");
    }

    abstract void doStuff(); // 無實作的抽象方法

}


 Abstract:

Abstract Class 一般都是在 Class 程式碼撰寫過程慢慢被發現, 進而從 Class 提升為 Abstract Class

盡量讓 Abstract Class 擁有最多的共用程式碼,盡量減少資料

Abstract Class 不能 Instance。即不能使用 New 關鍵字初始化

Abstract Class 是必須被衍生 Class 覆寫方法

只有參數宣告,沒有實作的方法,稱為 abstract method

某些情況下,雖然有實作,但我們希望強迫子類別必須 override 該方法時,也可以宣告為abstract method。

Interface 裡的方法一定沒有實作,因此必然為 abstract method。

衍生 Class 可以部份實作

如果 Class 中包含 Abstract Method (抽象方法),那麼此 Class 就必須定義為 Abstract Class

繼承 abstract class 的子類別必須 override 所有父類別的 abstract method,否則子類別也必須宣告為 abstract class。

一個 Class 只能繼承一個 Abstract Class


Interface:

Interface 比較像定規格,一般而言都是一開始就設計、定義,也是另一種的「藍圖」,
一開始在什麼都不知道的情況下,預先設計好相關架構

Interface不能 Instance,不能有建構式

不能有修飾詞,Public、Private…等。 (Public Interface)

對宣告中的變數

public: 外界觀看某物件時 ,所能看到的表象以及溝通的管道 ,所以 Interface 內的變數一定是 public。也就是說即便宣告時沒寫 public 關鍵字 ,Compiler 也會幫我們加上去。

final 成員,通常不希望變數會隨便更動

static:

對宣告中的方法

public: 外界觀看某物件時,所能看到的表象以及溝通的管道。同宣告中的變數之 public

abstract: Interface 沒有實作,裡面定義的 method 只是宣告而已。沒有實作的 method,在 Java 裡用 abstract 這個關鍵字來表達。

當 Interface 繼承多個 Interface ,或 Class 實作多個 Interface 時,如果有多個同名的函數或變數時,應該如何處理 ?

例如 Runnable 和 AnotherRun 這兩個界面都定義了變數 PERIOD 和方法 run。

相同變數名稱:由於 interface內的變數具有 static 的性質,因此使用這些變數時,必須加上 Interface 的名稱才行,如 Runnable.PERIOD,AnotherRun.PERIOD,因此不會造成任何混淆。

相同函數名稱:如果signature (參數個數,型態以及傳回值型態)完全相同,則 Class 只要實作一次即可,例如 Runnable 和 AnotherRun 均定義 void run(),因此 Class D 只要實作一次就好了。如果同名函數符合 Overloading,把它們分別當成不同的 method 即可。如果參數完全相同,但傳回值不同,則違反了Overloading 的原則,會產生 Compile Error。

                             一個 Class 可實作多個 Interface 

Wednesday, March 23, 2022

[Architecture] 寫 code 5 原則

1. 单一职责原则 SRP (解相依性高)

2. 开闭原则 (扩展是开放,修改是封闭的)

3. 里氏替换原则 (利用继承和多态) 

      以父类的形式声明的变量(或形参),赋值为任何继承于这个父类的子类后不影响程序的执行

      不存在继承于 View 但是却没实现draw函数的子类(abstract方法必须实现)

4. 依赖倒置原则 (实现解耦)

5. 接口隔离原则 (类之间的依赖关系应该建立在最小的接口上)

Reference:

https://juejin.cn/post/6844903437700710408#heading-15

Sunday, March 20, 2022

Android 几种进程通信方式

 RPC 即 Remote Procedure Call (远程过程调用) 是一种计算机通讯协议,它为我们定义了计算机 C 中的程序如何调用另外一台计算机 S 的程序,让程序员不需要操心底层网络协议,使得开发包括网络分布式多程序在内的应用程序更加容易。

RPC 是典型的 Client/Server 模式,由客户端对服务器发出若干请求,服务器收到后根据客户端提供的参数进行操作,然后将执行结果返回给客户端。

RPC 位于 OSI 模型中的会话层:(因為是有協議)


IDL 是什么

RPC 只是一种协议,规定了通信的规则。


在实际工作中客户端与服务端会有各种各样的平台,就好像日常开发一样,为了统一处理不同的实现,需要定义一个共同的接口,于是有了 IDL。

IDL 即 Interface Description Language (接口定义语言)。

它通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流。比如,一个组件用 C++ 写成,另一个组件用 Java 写,仍然可以通信。

IPC 是什么

IPC 即 Inter-Process Communication (进程间通信)。

Android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”。

“进程隔离”更详细的介绍(节选自:http://blog.csdn.net/u010132993/article/details/72582655):

但是在大多数情形下,不同进程间的数据通讯是不可避免的,因此操作系统必须提供跨进程通信机制。

Android 几种进程通信方式

文件

AIDL (基于 Binder)

Binder

Messenger (基于 Binder)

ContentProvider (基于 Binder)

Socket

Reference:

https://blog.csdn.net/u011240877/article/details/72863432


[C++] Primer - 函数参数传递中值传递、地址传递、引用传递有什么区别?


實參 (argument)

全称为"实际参数"是在调用时传递给函数的参数. 实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。

形参(parameter)

全称为"形式参数" 由于它不是实际存在变量,所以又称虚拟变量。是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数.在调用函数时,实参将赋值给形参。因而,必须注意实参的个数,类型应与形参一一对应,并且实参必须要有确定的值。

形式参数:形参是函数被调用时用于接收实参值的变量


(1) 值传递,会为形参重新分配内存空间,将实参的值拷贝给形参,形参的值不会影响实参的值,函数调用结束后形参被释放;


(2) 引用传递,不会为形参重新分配内存空间,形参只是实参的别名,形参的改变会影响实参的值,函数调用结束后形参不会被释放;


(3) 地址传递,形参为指针变量,将实参的地址传递给函数,可以在函数中改变实参的值,调用时为形参指针变量分配内存,结束时释放指针变量


Android Framework(AMS,WMS,PMS等)的概念及解析,获取系统服务

Framework API:

Activity Manager

Window Manager

Content Providers

View System

Notification Manager

Package Manager

Telephony Manager

Resource Manager...

其实所谓的AMS,PMS,以及WMS等都是运行在system_server这个进程中的线程

 Android的硬件抽象层,内核驱动

 Android的硬件抽象层,简单来说,就是对Linux内核驱动程序的封装,向上提供接口,屏蔽低层的实现细节。也就是说,把对硬件的支持分成了两层,一层放在用户空间(User Space),一层放在内核空间(Kernel Space),其中,硬件抽象层运行在用户空间,而Linux内核驱动程序运行在内核空间。为什么要这样安排呢?把硬件抽象层和内核驱动整合在一起放在内核空间不可行吗?从技术实现的角度来看,是可以的,然而从商业的角度来看,把对硬件的支持逻辑都放在内核空间,可能会损害厂家的利益。我们知道,Linux内核源代码版权遵循GNU License,而Android源代码版权遵循Apache License,前者在发布产品时,必须公布源代码,而后者无须发布源代码。

  Android才会想到把对硬件的支持分成硬件抽象层和内核驱动层,内核驱动层只提供简单的访问硬件逻辑,例如读写硬件寄存器的通道,至于从硬件中读到了什么值或者写了什么值到硬件中的逻辑,都放在硬件抽象层中去了,这样就可以把商业秘密隐藏起来了。也正是由于这个分层的原因,Android被踢出了Linux内核主线代码树中。大家想想,Android放在内核空间的驱动程序对硬件的支持是不完整的,把Linux内核移植到别的机器上去时,由于缺乏硬件抽象层的支持,硬件就完全不能用了,这也是为什么说Android是开放系统而不是开源系统的原因。

Client、Server、Service Manager和Binder驱动:

1. Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中

2. Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信

3.Client和Server之间的进程间通信通过Binder驱动程序间接实现

4. Service Manager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力 组件Service Manager,它是整个Binder机制的守护进程,用来管理开发者创建的各种Server,并且向Client提供查询Server远程接口的功能。

Zygote创建Java世界的步骤

· 第一天:创建AppRuntime对象,并调用它的start。此后的活动则由AppRuntime来控制。

· 第二天:调用startVm创建Java虚拟机,然后调用startReg来注册JNI函数。

· 第三天:通过JNI调用com.android.internal.os.ZygoteInit类的main函数,从此进入了Java世界。然而在这个世界刚开创的时候,什么东西都没有。

· 第四天:调用registerZygoteSocket。通过这个函数,它可以响应子孙后代的请求。同时Zygote调用preloadClasses和preloadResources,为Java世界添砖加瓦。

· 第五天:Zygote觉得自己工作压力太大,便通过调用startSystemServer分裂一个子进程system_server来为Java世界服务。

· 第六天:Zygote完成了Java世界的初创工作,它已经很满足了。下一步该做的就是调用runSelectLoopMode后,便沉沉地睡去了。

· 以后的日子:Zygote随时守护在我们的周围,当接收到子孙后代的请求时,它会随时醒来,为它们工作。

ActivityManagerService(AMS)

AMS是Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块相类似,因此它在Android中非常重要。

 -- 为了帮助读者更好地理解AMS,按五条不同的线来分析它:

·第一条线:同其他服务一样,将分析SystemServer中AMS的调用轨迹。

·第二条线:以am命令启动一个Activity为例,分析应用进程的创建、Activity的启动,以及它们和AMS之间的交互等知识。

·第三条线和第四条线:分别以Broadcast和Service为例,分析AMS中Broadcast和Service的相关处理流程。

·第五条线:以一个Crash的应用进程为出发点,分析AMS如何打理该应用进程的身后事。

-- ActivityManagerService提供的主要功能:

(1)统一调度各应用程序的Activity;

(2)内存管理;

(3)进程管理;

在整个系统中,Activity实际上有两个实体。

一个在应用进程中跟应用程序员打交道的Activity,

一个是在AMS的中具有管理功能的History Record。


參考資料:

https://blog.csdn.net/ShareUs/article/details/53125096

[C++] Primer - 大多数编译错误在实例化期间报告

模板直到实例化时才生成代码,所以获得模板代码编译错误的时机较晚。编译器在3个阶段报告错误:

1. 第一阶段是编译模板本身时,此时一般错误很少,只是检查语法错误,不检查依赖于类型的代码

第二个阶段是遇到使用模板时,对函数模板调用编译器会检查实参数目是否正确。还要检查参数类型是否匹配。对类模板来说,编译器检查用户是否提供了正确的模板实参,但也仅限于此。

第三个阶段是模板实例化时,只有这个阶段才能发现类型相关的错误。依赖于编译器如何管理实例化,这类错误可能在链接时才报告。

编写模板代码不能针对特定类型,但模板代码通常对其所用的类型有一些假设。比如compare就会假设实参支持<运算符。

如果实例化T类型不支持<,那么就会在第三个阶段报错。

[C++] Primer - 1.1.6 模板編譯

 编译器遇到一个模板定义时,并不生成代码。只有当实例化模板时编译器才生成代码。

当调用一个函数时,编译器只需要掌握函数的声明,函数定义不必已经出现,即使不定义,编译也会通过,最终会在链接时才发现undefined symbol这个熟悉的错误

但对于模板来说却不同,生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常既包含声明也包含定义。

函数模板和类模板成员函数定义通常在头文件中。


[C++] Primer - 1.1.5 编写类型无关的代码

泛型代码要适配各种用到的类型。compare虽然简单,但说明了编写泛型代码的两个重要原则:

1. 函数参数是const引用

2. 条件判断仅仅使用<运算符

通过将函数参数设定为const引用,就保证了函数可以用于不能拷贝的类型。不拷贝也提高了效率。

仅仅使用<运算符降低了compare对处理类型的要求,只要适配的类型支持<运算符,就可以应用compare。


归根结底,核心的思想就是让模板程序尽量降低对实参类型的要求

 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据

[C++] Primer - inline 和 constexpr 函数模板

 函数模板可以声明为inline或constexpr的,如同非模板函数一样

template <typename T>

inline T min(const T&, const T&);


[C++] Primer - 非类型模板参数

除了定义类型参数,还可以定义非类型参数(nontype parameter)。非类型参数表示一个值而非一个类型。

模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化模板。

template<unsigned N, unsigned M>

int compare(const char (&p1)[N], const char (&p2)[M])

{

    return strcmp(p1, p2);

}

处理两个字符数组,两个非类型模板参数是unsigned int值,该值在调用时确定,可以由程序员显式指定,也可以隐式推断

compare("hi", "mom");

//编译器会用字面常量的大小来代替N和M,从而实例化模板。

// int compare(const char (&p1)[3], const char (&p2)[4])

非类型参数可以是一个整型,或是一个指向对象或函数类型的指针或左值引用。

绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期。不能用一个普通局部变量或动态对象作为指针或引用非类型模板参数的实参。指针参数可以用nullptr或一个值为0的常量表达式来实例化。


模板定义内,模板非类型参数是一个常量值。在需要常量表达式的地方,可以使用非类型参数,例如,指定数组大小。

[C++] Primer - 模板類型參數

 返回类型和参数类型相同

template <typename T>

T foo(T* p)

{

    T tmp = *p; // tmp的类型将是指针p指向的类型

    //...

    return tmp;

}

如果类型参数不止一个,那么他们不能同名,且每一个前面都需要typename关键字,用逗号隔开。

template <typename T, typename U>

T calc(const T&, const U&);

Saturday, March 19, 2022

[C++] Primer - 模板与泛型编程

為何要用泛型

1.1 定義模板

  1.1.1 函數模板

    1.1.1.1 實例化模板

    1.1.1.2 模板類型參數

    1.1.1.3 非類型模板參數 (compare)

    1.1.1.4 inline和constexpr函数模板

    1.1.1.5 编写类型无关的代码

    1.1.1.6 模板編譯

    1.1.1.7 大多数编译错误在实例化期间报告

  1.1.2 類模板

  1.1.3 模板參數

  1.1.4成員模板

  1.1.5 控制實例化

1.2 模板實參推斷


2 模板实参推断

3  重载与模板

4 可變參數模板

5 模板特例化



Reference:

https://www.cnblogs.com/fortunely/p/14564383.html


[C++] Primer - 实例化模板

 实例化模板

由于模板compare只是一个pattern,所以在使用参数调用它时要先确定T的类型,这一过程叫做模板实例化

//实例化出int compare(const int &, const int &)

cout << compare(1,0) << endl; //T为int

//实例化出int compare(const vector<int>&, const vector<int>&)

vector<int> vec1{1, 2, 3}, vec2{4, 5, 6};

cout << compare(vec1, vec2) << endl; //T为vector<int>

Monday, March 14, 2022

[C++] Primer - 函數模板

当调用一个函数时,编译器通常用函数实参来推断模板实参,用此函数实参类型代替模板实参创建出一个新的“实例”,即一个可调用的函数,这个过程叫实例化(instantiate)函数模板。该函数是得到的一个特定版本的函数。

编译器生成的函数版本,通常称为模板的实例。

 int compare(const string &v1, const string &v2)

{

    if(v1 < v2) return -1;

    if(v2 < v1) return 1;

    return 0;

}

int compare(const double &v1, const double &v2)

{

    if(v1 < v2) return -1;

    if(v2 < v1) return 1;

    return 0;

}

// 定义compare的函数模板 // compare声明了类型为T的类型参数 // template关键字,<typename T>是模板参数列表,typename和class关键字等价,都可以使用,T是模板参数,以逗号分隔其他模板参数

template <typename T> int compare(const T &v1, const T &v2) { if (v1 < v2) return -1; else if (v1 > v2) return 1; return 0; }

// 调用模板,将模板实参绑定到模板参数(T)上 // 调用函数模板时,编译器根据函数实参(1,0)来推断模板实参

cout << compare(1, 0) << endl; // 推断T为int

// 编译器会根据调用情况,推断出T为int,从而生成一个compare版本,T被替换为int

int compare(const T &v1, const T &v2)

{

    if(v1 < v2) return -1;

    if(v2 < v1) return 1;

    return 0;

}


Saturday, March 12, 2022

[Project] Impact Mapping

 一種視覺化方式,為了建立商業目標與產品功能的關係


影響地圖(Impact Mapping)

它是由 Gojko Adzic 《Impact Mapping: Making a Big Impact With Software Products and Projects》提出的一種思考分析的方法: 透過視覺化方式,建立商業目標與產品功能的關係,以及背後關聯的假設。讓團隊可以在資訊共享的狀況下,讓業務部門和軟體部門一起討論, 做出正確的產品。

主要是通過以下4個問題來進行分析和討論。

Why?

就是目標、需求,需要發掘的是最真實的好目標。舉個最著名的需求例子:如果我們問以前的人他們想要什麼?他們應該會告訴我們「要一匹更快的馬!」但其實用戶真正想要的是更快速到達目的地的工具,若我們真的按照用戶提供的解決方法,那就不是最真實的好目標。

什麼是好的目標呢?Gojko Adzic 在書中提到 SMART 理論:

- Specific(明確的)

- Measurable(可度量的)

- Action-oriented(能驅動行動的)

- Realistic(實際的)

- Timely(有時限的)


例如:計劃在2019 Q1 網路廣告收益上升30%


Who?


有了目標後,就是誰會被他影響?誰能產生需要的效果?誰會阻礙他?

書中提到3個角色:


- 主要角色:實現目標的人

- 次要角色:服務提供者

- 場外角色:不直接受益或提供服務的人


可利用人物誌(Persona),定義出角色,例如:喜歡玩手遊的年輕男性、25–35歲有網購習慣的上班族。


How?


他們怎樣幫助我們達成目標?他們如何幫助或妨礙我們取得成功?對這個角色而言,你希望他做出什麼改變?角色的行爲是如何改變的?


只需要列出對接近目標有幫助的影響,例如:增加上線時數、點擊廣告意願提升


What?


你想要做什麼,來對這個角色產生你希望的影響?

考慮任何有助於實現影響的手段以達成目標,有些時候好的『行銷活動』可能還勝於把想要的『功能』寫完寫好。

可以做出來的、和目標相關的、有效的,例如:建立玩家交流社群、設計互動式廣告。


Reference:

https://www.projectup.net/article/view/id/16688

https://www.projectclub.com.tw/hard-power/gibson_projectmanagement/1728-impact-mapping.html

[Project] index

範籌

時程

預算


Impact Mapping (一種視覺化方式,為了建立商業目標與產品功能的關係)

User Journey Map (潛在顧客的最佳指南)



Friday, March 11, 2022

[Architecture] 三层架构

分层的核心任务是“高内聚低耦合”的实现。

三层架构:

表示层(UI)

业务逻辑层(BLL)

数据访问层(DAL)



各层之间采用接口相互访问,并通过对象模型的实体类(Model)作为数据传递的载体,不同的对象模型的实体类一般对应于数据库的不同表,实体类的属性与数据库表的字段名一致

分層方式:

数据层

不包含任何代码,只有数据库,还有相关的存储过程。这种模式下,数据层看起来就变得很简单了。只包含所建立的数据库和一些存储过程(注意是存储过程)。其实这些存储过程的建立也是相当复杂的,因为它们可以完成除数据访问外的其他一些很强大的功能,如分页、实现搜索算法等。

数据访问的逻辑就都放在业务层

当然业务层还包含其他一些逻辑代码。我们来看一个示例,假设数据库里有一个表 BOOKS(书),建立一个存储过程 GetAllBooks,用来读取书的信息,这样在业务层里编一个方法 GetBookS()和一个公用数据库访问类,GetBooks()就通过数据库访问类打开连接,执行在存储过程,返回数据 (返回类型可以是 DataT - able,DataSet,DataReader 或 者 实 体 类)。业务层单独编译成一个或者几个 DLL 文件。接着就是表示层了,表示层通过调用GetBookS()返回数据绑定在相关的控件里。业务层的方法都是在表示层调用。一般来说 book.aspx 和 book.aspx.cs 都是表示层的内容,所有前台的设计、相关控件、数据缓存都是属于表示层

数据层还包含所有公共数据访问代码。这种模式和前一种差别不大,主要是把数据访问代码留到数据层。这样可以很方便地实现对多数据库的支持。业务逻辑层直接调用数据层的相关访问数据的代码,完全不必了解底层是什么数据库。其他和前一种没什么分别


Reference:

https://baike.baidu.com/item/%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84/11031448

Sunday, March 6, 2022

[c] 巨集生成程式碼

巨集實際的作用: generate (產生/生成) 程式碼。

 

#define DECLARE_OBJECT(name) \

struct __##name##_node; \

typedef struct __##name##_node *name##_node; \

struct __##name##_node { \

name element; \

name##_node next; \

}; \

void append_##name(const name *X, name##_node *list); \

void delete_##name##_list(name##_node *list);


DECLARE_OBJECT(light)

DECLARE_OBJECT(rectangular)

DECLARE_OBJECT(sphere)


light 在 DECLARE_OBJECT(light) 中會取代 name,因此會產生以下程式碼:

struct __light_node;

typedef struct __light_node *light_node;

struct __light_node { light element; light_node next; };

void append_light(const light *X, light_node *list);

void delete_light_list(light_node *list);


可用 gcc -E -P 觀察輸出:



typedef enum { NORTH, SOUTH, EAST, WEST} Direction;

typedef struct {

    char *description;

    int (*init)(void *self);

    void (*describe)(void *self);

    void (*destroy)(void *self);

    void *(*move)(void *self, Direction direction);

    int (*attack)(void *self, int damage);

} Object;

int Object_init(void *self);

void Object_destroy(void *self);

void Object_describe(void *self);

void *Object_move(void *self, Direction direction);

int Object_attack(void *self, int damage);

void *Object_new(size_t size, Object proto, char *description);

#define NEW(T, N) Object_new(sizeof(T), T##Proto, N)

#define _(N) proto.N

n8n index

 【n8n免費本地端部署】Windows版|程式安裝x指令大補帖  【一鍵安裝 n8n】圖文教學,獲得無限額度自動化工具&限時免費升級企業版功能