Spring7——開發基於註解形式的spring

開發基於註解形式的spring
SpringIOC容器的2種形式:
(1)xml配置文件:applicationContext.xml; 存bean:<bean> 取bean:

ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");

(2)註解:帶有@Configuration註解的類(配置類)

存bean:@Bean+方法的返回值

//配置類,相當於applicationContext.xml
@Configuration
public class MyConfig {


    @Bean //id=方法名(myStudent)
    public Student myStudent(){
        Student student=new Student(2,"fg",34);
        return student;
    }
}

取bean: 

ApplicationContext context=new AnnotationConfigApplicationContext(MyConfig.class);
Student myStudent = (Student) context.getBean("myStudent");

注意:兩種形式獲取的IOC是獨立的  
註解形式向IOC容器存放bean詳解:
1.必須有@Configuration
2.形式:
2.1 三層組件(Controller、Service、Dao):             (1)將三層組件分別加註解@Controller、@Service、@Repository等價於@Commponent              (2)納入掃描器 a.xml配置

<context:component-scan base-package="org.ghl.controller"></context:component-scan>

b.註解形式      component-scan只對三層組件負責。    
給掃描器指定規則:                 過濾類型:FilterType(ANNOTATION, ASSIGNABLE_TYPE, CUSTOM)                 ANNOTATION:三層註解類型@Controller、@Service、@Repository等價於@Commponent

排除:
@ComponentScan(value = "org.ghl",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION ,value = {Service.class,Repository.class})})
包含:(有默認行為,可以通過useDefaultFilters禁止)
@ComponentScan(value = "org.ghl",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION ,value = {Service.class,Repository.class})},useDefaultFilters = false)

  ASSIGNABLE_TYPE:指具體的類。

@ComponentScan(value = "org.ghl",includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE ,value = {StudentController.class})},useDefaultFilters = false)

區分:ANNOTATION:Service.class指標有@Service的所有類;           ASSIGNABLE_TYPE:指具體的類。                 CUSTOM:自定義:自己定義規則

@ComponentScan(value = "org.ghl",includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM ,value = {MyFilter.class})},useDefaultFilters = false)

 

public class MyFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //拿到掃描器value = "org.ghl"包中的所有標有三層組件註解類的名字
        String className = classMetadata.getClassName();
        //只過濾出和student相關的三層組件
        if (className.contains("Student")){
            return true;  //表示包含
        }
        return false; //表示排除
    }
}

  

2.2 非三層組件(Student.clss/轉換器等): (1)@Bean+方法的返回值,id的默認值為方法名,也可以通過@Bean(“stu”)修改。 (2)import/FactoryBean  
bean的作用域

(@Scope(“singleton”))scope=”singleton”:單例 scope=”prototype”:原型、多實例。
執行的時機(產生bean的時機):         singleton:容器在初始化時,就創建對象,且只創建一次; 也支持延遲加載:在第一次使用時,創建對象。在config中加入@Lazy。         prototype:容器在初始化時,不創建對象,在每次使用時(每次從容器獲取對象時),再創建對象。         
條件註解 可以讓某一個Bean在某些條件下加入IOC容器。 (1)準備bean; (2)增加條件bean:給每個bean設置條件,必須實現Condition接口。 (3)根據條件加入IOC容器
  回顧給IOC加入Bean的方法:        註解:全部在@Configuration配置中設置:                 三層組件:掃描器+三層註解                 非三層組件:(1)@Bean+返回值                                      (2)@import                                      (3)FactoryBean(工廠Bean)
 
@import使用:         (1)直接編寫到@Import中; @Import({Apple.class,Banana.class})         (2)自定義ImportSelector接口的實現類,通過selectimports方法實現(方法的返回值就是要納入IOC容器的Bean)。並告知程序自己編寫的實現類。

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"org.ghl.entity.Apple","org.ghl.entity.Banana"}; //方法的返回值就是要納入IOC容器的Bean
    }
}
@Import({MyImportSelector.class})

    (3)編寫ImporBeanDefinitionRegistrar接口的實現類並重寫方法。 

public class MyImporBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //BeanDefinition beanDefinition=new RootBeanDefinition(Orange.class);
        BeanDefinition beanDefinition=new RootBeanDefinition("org.ghl.entity.Orange");
        beanDefinitionRegistry.registerBeanDefinition("myorange",beanDefinition);


    }
}  
@Import({MyImporBeanDefinitionRegistrar.class})

  

FactoryBean(工廠Bean)         1.寫實現類和重寫方法;  

public class MyFactoryBean implements FactoryBean{
    @Override
    public Object getObject() throws Exception {
        return new Apple();
    }

    @Override
    public Class<?> getObjectType() {
        return Apple.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

  2.註冊到@Bean中  

@Bean
public FactoryBean<Apple>  myFactoryBean(){
    return new MyFactoryBean();
}

注意:需要通過&區分獲取的對象是哪一個。不加&,獲取的是最內部真實的apple,如果加&,獲取的是FactoryBean。  

  

  

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

Spring Boot2+Resilience4j實現容錯之Bulkhead

Resilience4j是一個輕量級、易於使用的容錯庫,其靈感來自Netflix Hystrix,但專為Java 8和函數式編程設計。輕量級,因為庫只使用Vavr,它沒有任何其他外部庫依賴項。相比之下,Netflix Hystrix對Archaius有一個編譯依賴關係,Archaius有更多的外部庫依賴關係,如Guava和Apache Commons。

Resilience4j提供高階函數(decorators)來增強任何功能接口、lambda表達式或方法引用,包括斷路器、速率限制器、重試或艙壁。可以在任何函數接口、lambda表達式或方法引用上使用多個裝飾器。優點是您可以選擇所需的裝飾器,而無需其他任何東西。

有了Resilience4j,你不必全力以赴,你可以選擇你需要的。

https://resilience4j.readme.io/docs/getting-started

概覽

Resilience4j提供了兩種艙壁模式(Bulkhead),可用於限制併發執行的次數:

  • SemaphoreBulkhead(信號量艙壁,默認),基於Java併發庫中的Semaphore實現。
  • FixedThreadPoolBulkhead(固定線程池艙壁),它使用一個有界隊列和一個固定線程池。

本文將演示在Spring Boot2中集成Resilience4j庫,以及在多併發情況下實現如上兩種艙壁模式。

引入依賴

在Spring Boot2項目中引入Resilience4j相關依賴

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-bulkhead</artifactId>
    <version>1.4.0</version>
</dependency>

由於Resilience4j的Bulkhead依賴於Spring AOP,所以我們需要引入Spring Boot AOP相關依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

我們可能還希望了解Resilience4j在程序中的運行時狀態,所以需要通過Spring Boot Actuator將其暴露出來

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

實現SemaphoreBulkhead(信號量艙壁)

resilience4j-spring-boot2實現了對resilience4j的自動配置,因此我們僅需在項目中的yml/properties文件中編寫配置即可。

SemaphoreBulkhead的配置項如下:

屬性配置 默認值 含義
maxConcurrentCalls 25 艙壁允許的最大并行執行量
maxWaitDuration 0 嘗試進入飽和艙壁時,應阻塞線程的最長時間。

添加配置

示例(使用yml):

resilience4j.bulkhead:
  configs:
    default:
      maxConcurrentCalls: 5
      maxWaitDuration: 20ms
  instances:
    backendA:
      baseConfig: default
    backendB:
      maxWaitDuration: 10ms
      maxConcurrentCalls: 20

如上,我們配置了SemaphoreBulkhead的默認配置為maxConcurrentCalls: 5,maxWaitDuration: 20ms。並在backendA實例上應用了默認配置,而在backendB實例上使用自定義的配置。這裏的實例可以理解為一個方法/lambda表達式等等的可執行單元。

編寫Bulkhead邏輯

定義一個受SemaphoreBulkhead管理的Service類:

@Service
public class BulkheadService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private BulkheadRegistry bulkheadRegistry;

    @Bulkhead(name = "backendA")
    public JsonNode getJsonObject() throws InterruptedException {
        io.github.resilience4j.bulkhead.Bulkhead.Metrics metrics = bulkheadRegistry.bulkhead("backendA").getMetrics();
        logger.info("now i enter the method!!!,{}<<<<<<{}", metrics.getAvailableConcurrentCalls(), metrics.getMaxAllowedConcurrentCalls());
        Thread.sleep(1000L);
        logger.info("now i exist the method!!!");
        return new ObjectMapper().createObjectNode().put("file", System.currentTimeMillis());
    }
}

如上,我們將@Bulkhead註解放到需要管理的方法上面。並且通過name屬性指定該方法對應的Bulkhead實例名字(這裏我們指定的實例名字為backendA,所以該方法將會利用默認的配置)。

定義接口類:

@RestController
public class BulkheadResource {
    @Autowired
    private BulkheadService bulkheadService;

    @GetMapping("/json-object")
    public ResponseEntity<JsonNode> getJsonObject() throws InterruptedException {
        return ResponseEntity.ok(bulkheadService.getJsonObject());
    }
}

編寫測試:

首先添加測試相關依賴

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>3.0.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>4.0.2</version>
    <scope>test</scope>
</dependency>

這裏我們使用rest-assured和awaitility編寫多併發情況下的API測試

public class SemaphoreBulkheadTests extends Resilience4jDemoApplicationTests {
    @LocalServerPort
    private int port;
    @BeforeEach
    public void init() {
        RestAssured.baseURI = "http://localhost";
        RestAssured.port = port;
    }

    @Test
    public void 多併發訪問情況下的SemaphoreBulkhead測試() {
        CopyOnWriteArrayList<Integer> statusList = new CopyOnWriteArrayList<>();
        IntStream.range(0, 8).forEach(i -> CompletableFuture.runAsync(() -> {
                statusList.add(given().get("/json-object").statusCode());
            }
        ));
        await().atMost(1, TimeUnit.MINUTES).until(() -> statusList.size() == 8);
        System.out.println(statusList);
        assertThat(statusList.stream().filter(i -> i == 200).count()).isEqualTo(5);
        assertThat(statusList.stream().filter(i -> i == 500).count()).isEqualTo(3);
    }
}

可以看到所有請求中只有前五個順利通過了,其餘三個都因為超時而導致接口報500異常。我們可能並不希望這種不友好的提示,因此Resilience4j提供了自定義的失敗回退方法。當請求併發量過大時,無法正常執行的請求將進入回退方法。

首先我們定義一個回退方法

private JsonNode fallback(BulkheadFullException exception) {
        return new ObjectMapper().createObjectNode().put("errorFile", System.currentTimeMillis());
    }

注意:回退方法應該和調用方法放置在同一類中,並且必須具有相同的方法簽名,並且僅帶有一個額外的目標異常參數。

然後在@Bulkhead註解中指定回退方法:@Bulkhead(name = "backendA", fallbackMethod = "fallback")

最後修改API測試代碼:

@Test
public void 多併發訪問情況下的SemaphoreBulkhead測試使用回退方法() {
    CopyOnWriteArrayList<Integer> statusList = new CopyOnWriteArrayList<>();
    IntStream.range(0, 8).forEach(i -> CompletableFuture.runAsync(() -> {
            statusList.add(given().get("/json-object").statusCode());
        }
    ));
    await().atMost(1, TimeUnit.MINUTES).until(() -> statusList.size() == 8);
    System.out.println(statusList);
    assertThat(statusList.stream().filter(i -> i == 200).count()).isEqualTo(8);
}

運行單元測試,成功!可以看到,我們定義的回退方法,在請求過量時起作用了。

實現FixedThreadPoolBulkhead(固定線程池艙壁)

FixedThreadPoolBulkhead的配置項如下:

配置名稱 默認值 含義
maxThreadPoolSize Runtime.getRuntime().availableProcessors() 配置最大線程池大小
coreThreadPoolSize Runtime.getRuntime().availableProcessors() - 1 配置核心線程池大小
queueCapacity 100 配置隊列的容量
keepAliveDuration 20ms 當線程數大於核心時,這是多餘空閑線程在終止前等待新任務的最長時間

添加配置

示例(使用yml):

resilience4j.thread-pool-bulkhead:
  configs:
    default:
      maxThreadPoolSize: 4
      coreThreadPoolSize: 2
      queueCapacity: 2
  instances:
    backendA:
      baseConfig: default
    backendB:
      maxThreadPoolSize: 1
      coreThreadPoolSize: 1
      queueCapacity: 1

如上,我們定義了一段簡單的FixedThreadPoolBulkhead配置,我們指定的默認配置為:maxThreadPoolSize: 4,coreThreadPoolSize: 2,queueCapacity: 2,並且指定了兩個實例,其中backendA使用了默認配置而backendB使用了自定義的配置。

編寫Bulkhead邏輯

定義一個受FixedThreadPoolBulkhead管理的方法:

@Bulkhead(name = "backendA", type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<JsonNode> getJsonObjectByThreadPool() throws InterruptedException {
    io.github.resilience4j.bulkhead.ThreadPoolBulkhead.Metrics metrics = threadPoolBulkheadRegistry.bulkhead("backendA").getMetrics();
    logger.info("now i enter the method!!!,{}", metrics);
    Thread.sleep(1000L);
    logger.info("now i exist the method!!!");
    return CompletableFuture.supplyAsync(() -> new ObjectMapper().createObjectNode().put("file", System.currentTimeMillis()));
}

如上定義和SemaphoreBulkhead的方法大同小異,其中@Bulkhead显示指定了type的屬性為Bulkhead.Type.THREADPOOL,表明其方法受FixedThreadPoolBulkhead管理。由於@Bulkhead默認的BulkheadSemaphoreBulkhead,所以在未指定type的情況下為SemaphoreBulkhead。另外,FixedThreadPoolBulkhead只對CompletableFuture方法有效,所以我們必創建返回CompletableFuture類型的方法。

定義接口類方法

@GetMapping("/json-object-with-threadpool")
public ResponseEntity<JsonNode> getJsonObjectWithThreadPool() throws InterruptedException, ExecutionException {
    return ResponseEntity.ok(bulkheadService.getJsonObjectByThreadPool().get());
}

編寫測試代碼

@Test
public void 多併發訪問情況下的ThreadPoolBulkhead測試() {
    CopyOnWriteArrayList<Integer> statusList = new CopyOnWriteArrayList<>();
    IntStream.range(0, 8).forEach(i -> CompletableFuture.runAsync(() -> {
            statusList.add(given().get("/json-object-with-threadpool").statusCode());
        }
    ));
    await().atMost(1, TimeUnit.MINUTES).until(() -> statusList.size() == 8);
    System.out.println(statusList);
    assertThat(statusList.stream().filter(i -> i == 200).count()).isEqualTo(6);
    assertThat(statusList.stream().filter(i -> i == 500).count()).isEqualTo(2);
}

測試中我們并行請求了8次,其中6次請求成功,2次失敗。根據FixedThreadPoolBulkhead的默認配置,最多能容納maxThreadPoolSize+queueCapacity次請求(根據我們上面的配置為6次)。

同樣,我們可能並不希望這種不友好的提示,那麼我們可以指定回退方法,在請求無法正常執行時使用回退方法。

private CompletableFuture<JsonNode> fallbackByThreadPool(BulkheadFullException exception) {
    return CompletableFuture.supplyAsync(() -> new ObjectMapper().createObjectNode().put("errorFile", System.currentTimeMillis()));
}
@Bulkhead(name = "backendA", type = Bulkhead.Type.THREADPOOL, fallbackMethod = "fallbackByThreadPool")
public CompletableFuture<JsonNode> getJsonObjectByThreadPoolWithFallback() throws InterruptedException {
    io.github.resilience4j.bulkhead.ThreadPoolBulkhead.Metrics metrics = threadPoolBulkheadRegistry.bulkhead("backendA").getMetrics();
    logger.info("now i enter the method!!!,{}", metrics);
    Thread.sleep(1000L);
    logger.info("now i exist the method!!!");
    return CompletableFuture.supplyAsync(() -> new ObjectMapper().createObjectNode().put("file", System.currentTimeMillis()));
}

編寫測試代碼

@Test
public void 多併發訪問情況下的ThreadPoolBulkhead測試使用回退方法() {
    CopyOnWriteArrayList<Integer> statusList = new CopyOnWriteArrayList<>();
    IntStream.range(0, 8).forEach(i -> CompletableFuture.runAsync(() -> {
            statusList.add(given().get("/json-object-by-threadpool-with-fallback").statusCode());
        }
    ));
    await().atMost(1, TimeUnit.MINUTES).until(() -> statusList.size() == 8);
    System.out.println(statusList);
    assertThat(statusList.stream().filter(i -> i == 200).count()).isEqualTo(8);
}

由於指定了回退方法,所有請求的響應狀態都為正常了。

總結

本文首先簡單介紹了Resilience4j的功能及使用場景,然後具體介紹了Resilience4j中的Bulkhead。演示了如何在Spring Boot2項目中引入Resilience4j庫,使用代碼示例演示了如何在Spring Boot2項目中實現Resilience4j中的兩種Bulkhead(SemaphoreBulkhead和FixedThreadPoolBulkhead),並編寫API測試驗證我們的示例。

本文示例代碼地址:https://github.com/cg837718548/resilience4j-demo

歡迎訪問筆者博客:blog.dongxishaonian.tech

關注筆者公眾號,推送各類原創/優質技術文章 ⬇️

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

如果人生也能存檔——C#中的備忘錄模式

大家好,老胡又和大家見面了。首先承認今天的博客有點標題黨了,人生是沒有存檔,也沒有後悔葯的。有存檔和後悔葯的,那是遊戲,不知道這是不是遊戲讓人格外放鬆的原因之一。

今天恰逢端午放假,就讓我們來試着做一個小遊戲吧,順帶看看備忘錄模式是如何在這種情況下面工作的。

遊戲背景

這是一個簡單的打怪遊戲,有玩家,有怪獸,玩家作為主角光環,有如下三個特殊能力

  • 攻擊怪獸有暴擊幾率
  • 有幾率迴避怪獸攻擊
  • 可以自己治療一定生命值

遊戲實現

角色類
角色基類

首先是角色類,角色類提供玩家和怪獸最基本的抽象,比如血量、攻擊力、攻擊和治療。(對於怪獸來說,治療是沒有提供實現的,壞人肯定不能再治療了)

class Character
{
    public int HealthPoint { get; set; }
    public int AttackPoint { get; set; }        

    public virtual void AttackChracter(Character opponent)
    {
        opponent.HealthPoint -= this.AttackPoint;
        if (opponent.HealthPoint < 0)
        {
            opponent.HealthPoint = 0;
        }
    }

    public virtual void Cure()
    {
		//故意留空給子類實現
    }
}
玩家類

玩家實現了治療功能並且有暴擊幾率。

class Player : Character
{
    private float playerCriticalPossible;
    public Player(float critical)
    {
        playerCriticalPossible = critical;
    }

    public override void AttackChracter(Character opponent)
    {
        base.AttackChracter(opponent);
        Console.WriteLine("Player Attacked Monster");

        Random r = new Random();
        bool critical = r.Next(0, 100) < playerCriticalPossible * 100;
        if (critical)
        {
            base.AttackChracter(opponent);
            Console.WriteLine("Player Attacked Monster again");
        }
    }

    public override void Cure()
    {
        Random r = new Random();
        HealthPoint += r.Next(5, 10);
        Console.WriteLine("Player cured himself");
    }
}
怪獸類

怪獸沒有治療能力但是有一定的幾率丟失攻擊目標。

class Monster : Character
{
    private float monsterMissingPossible;
    public Monster(float missing)
    {
        monsterMissingPossible = missing;
    }

    public override void AttackChracter(Character opponent)
    {
        Random r = new Random();
        bool missing = r.Next(0, 100) < monsterMissingPossible * 100;
        if (missing)
        {
            Console.WriteLine("Monster missed it");
        }
        else
        {
            base.AttackChracter(opponent);
            Console.WriteLine("Monster Attacked player");
        }
    }
}
遊戲類

遊戲類負責實例化玩家和怪獸、記錄回合數、判斷遊戲是否結束,暴露可調用的公共方法給遊戲操作類。

class Game
{
    private Character m_player;
    private Character m_monster;
    private int m_round;
    private float playerCriticalPossible = 0.6f;
    private float monsterMissingPossible = 0.2f;
    
    public Game()
    {
        m_player = new Player(playerCriticalPossible)
        {
            HealthPoint = 15,
            AttackPoint = 2
        };
        m_monster = new Monster(monsterMissingPossible)
        {
            HealthPoint = 20,
            AttackPoint = 6
        };
    }

    public bool IsGameOver => m_monster.HealthPoint == 0 || m_player.HealthPoint == 0;

    public void AttackMonster()
    {            
        m_player.AttackChracter(m_monster);
    }

    public void AttackPlayer()
    {
        m_monster.AttackChracter(m_player);
    }

    public void CurePlayer()
    {
        m_player.Cure();
    }

    public void BeginNewRound()
    {
        m_round++;
    }

    public void ShowGameState()
    {
        Console.WriteLine("".PadLeft(20, '-'));
        Console.WriteLine("Round:{0}", m_round);
        Console.WriteLine("player health:{0}", "".PadLeft(m_player.HealthPoint, '*'));
        Console.WriteLine("monster health:{0}", "".PadLeft(m_monster.HealthPoint, '*'));
    }
}
遊戲操作類

在我們這個簡易遊戲中,沒有UI代碼,遊戲操作類負責在用戶輸入和遊戲中搭建一個橋樑,解釋用戶的輸入。

class GameRunner
{
    private Game m_game;
    public GameRunner(Game game)
    {
        m_game = game;
    }

    public void Run()
    {
        while (!m_game.IsGameOver)
        {
            m_game.BeginNewRound();
            bool validSelection = false;
            while (!validSelection)
            {
            	m_game.ShowGameState();
                Console.WriteLine("Make your choice: 1. attack 2. Cure");
                var str = Console.ReadLine();
                if (str.Length != 1)
                {
                    continue;
                }
                switch (str[0])
                {
                    case '1':
                        {
                            validSelection = true;
                            m_game.AttackMonster();
                            break;
                        }
                    case '2':
                        {
                            validSelection = true;
                            m_game.CurePlayer();
                            break;
                        }
                    default:
                        break;
                }
            }
            if(!m_game.IsGameOver)
            {
                m_game.AttackPlayer();
            }
        }            
    }
}
客戶端

客戶端的代碼就非常簡單了,只需要實例化一個遊戲操作類,然後讓其運行就可以了。

class Program
{
    static void Main(string[] args)
    {
        Game game = new Game();
        GameRunner runner = new GameRunner(game);
        runner.Run();
    }
}

試着運行一下,

看起來一切都好。

 

加上存檔

雖然遊戲可以正常運行,但是總感覺還是少了點什麼。嗯,存檔功能,一個遊戲沒有存檔是不健全的,畢竟,人生雖然沒有存檔,但是遊戲可是有的!讓我們加上存檔功能吧,首先想想怎麼設計。
 

需要存檔的數據

首先我們要明確,有哪些數據是需要存檔的,在這個遊戲中,玩家的生命值、攻擊力、暴擊率;怪獸的生命值、攻擊力和丟失率,遊戲的回合數,都是需要存儲的對象。
 

存檔定義

這是一個需要仔細思考的地方,一般來說,需要考慮以下幾個地方:

  • 存檔需要訪問一些遊戲中的私有字段,比如暴擊率,需要在不破壞遊戲封裝的情況下實現這個功能
  • 存檔自身需要實現信息隱藏,即除了遊戲,其他類不應該訪問存檔的詳細信息
  • 存檔不應該和遊戲存放在一起,以防不經意間遊戲破壞了存檔數據,應該有專門的類存放存檔

 

備忘錄模式出場

這個時候應該是主角出場的時候了。看看備忘錄模式的定義

在不破壞封閉的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。這樣以後就可將該對象恢復到原先保存的狀態

再看看UML,

看起來完全符合我們的需求啊,Originator就是遊戲類,知道如何創造存檔和從存檔中恢復狀態,Memento類就是存檔類,Caretaker是一個新類,負責保存存檔。

經過思考,我們決定採取備忘錄模式,同時加入以下措施:

  • 將存檔定義為遊戲中的私有嵌套類,這樣存檔可以毫無壓力的訪問遊戲中的私有字段,同時外界永遠沒有辦法去實例化或者嘗試通過轉型來獲得這個類,完美的保護了存檔類
  • 存檔類是一個簡單的數據集合,不包含任何其他邏輯
  • 添加一個存檔管理器,可以放在遊戲操作類中,可以通過它看到我們當前有沒有存檔
  • 存檔放在存檔管理器中
  • 存檔實現一個空接口,在存檔管理器中以空接口形式出現,這樣外部類在訪問存檔的時候,僅能看到這個空接口。而在遊戲類內部,我們在使用存檔之前先通過向下轉型實現類型轉換(是的,向下轉型不怎麼好,但是偶爾可以用一下)
代碼實現
空接口
interface IGameSave
{

}
私有嵌套存檔類

該類存放在game裏面,無壓力地在不破壞封裝的情況下訪問game私有字段

private class GameSave : IGameSave
{
    public int PlayerHealth { get; set; }
    public int PlayerAttack { get; set; }
    public float PlayerCritialAttackPossible { get; set; }
    public int MonsterHealth { get; set; }
    public int MonsterAttack { get; set; }
    public float MonsterMissingPossible { get; set; }
    public int GameRound { get; set; }
}
創建存檔和從存檔恢復

game中添加創建存檔和從存檔恢復的代碼,在從存檔恢復的時候,使用了向下轉型,因為從存檔管理器讀出來的只是空接口而已

public IGameSave CreateSave()
{
    var save = new GameSave()
    {
        PlayerHealth = m_player.HealthPoint,
        PlayerAttack = m_player.AttackPoint,
        PlayerCritialAttackPossible = playerCriticalPossible,
        MonsterAttack = m_monster.AttackPoint,
        MonsterHealth = m_monster.HealthPoint,
        MonsterMissingPossible = monsterMissingPossible,
        GameRound = m_round
    };
    Console.WriteLine("game saved");
    return save;
}

public void RestoreFromGameSave(IGameSave gamesave)
{
    GameSave save = gamesave as GameSave;
    if(save != null)
    {
        m_player = new Player(save.PlayerCritialAttackPossible) { HealthPoint = save.PlayerHealth, AttackPoint = save.PlayerAttack };
        m_monster = new Player(save.MonsterMissingPossible) { HealthPoint = save.MonsterHealth, AttackPoint = save.MonsterAttack };
        m_round = save.GameRound;
    }
    Console.WriteLine("game restored");
}	
存檔管理器類

添加一個類專門管理存檔,此類非常簡單,只有一個存檔,要支持多存檔可以考慮使用List

    class GameSaveStore
    {
        public IGameSave GameSave { get; set; }
    }
在遊戲操作類添加玩家選項

首先在遊戲操作類中添加一個存檔管理器

private GameSaveStore m_gameSaveStore = new GameSaveStore();

接着修改Run方法添加用戶操作

public void Run()
{
    while (!m_game.IsGameOver)
    {
        m_game.BeginNewRound();
        bool validSelection = false;
        while (!validSelection)
        {
            m_game.ShowGameState();
            Console.WriteLine("Make your choice: 1. attack 2. Cure 3. Save 4. Load");
            var str = Console.ReadLine();
            if (str.Length != 1)
            {
                continue;
            }
            switch (str[0])
            {
                case '1':
                    {
                        validSelection = true;
                        m_game.AttackMonster();
                        break;
                    }
                case '2':
                    {
                        validSelection = true;
                        m_game.CurePlayer();
                        break;
                    }
                case '3':
                    {
                        validSelection = false;
                        m_gameSaveStore.GameSave = m_game.CreateSave();
                        break;
                    }
                case '4':
                    {
                        validSelection = false;
                        if(m_gameSaveStore.GameSave == null)
                        {
                            Console.WriteLine("no save to load");
                        }
                        else
                        {
                            m_game.RestoreFromGameSave(m_gameSaveStore.GameSave);
                        }
                        break;
                    }
                default:
                    break;
            }
        }
        if(!m_game.IsGameOver)
        {
            m_game.AttackPlayer();
        }
    }            
}

注意,上面的3和4是新添加的存檔相關的操作。試着運行一下。

看起來一切正常,這樣我們就使用備忘錄模式,完成了存檔讀檔的功能。

 

結語

這就是備忘錄模式的使用,如果大家以後遇到這種場景

  • 想要保存狀態,又不想破壞封裝
  • 需要把狀態保存到其他地方

那麼就可以考慮使用這個模式。

遊戲有存檔,人生沒存檔,願我們把握當下,天天努力。
祝大家端午安康,下次見。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

世界最快螞蟻 步頻是波爾特10倍以上

摘錄自2019年10月17日自由時報報導

報導,德國烏爾姆大學(University of Ulm University)生物行為專家普費弗(Sarah Pfeffer)所率領的團隊,發現撒哈拉銀蟻是世界上最快的螞蟻,每秒秒速將近1公尺,看起來雖然不多,但驚人步頻(跑步時腳步交換的頻率)可是世界百米紀錄保持人波爾特(Usain Bolt)的10倍以上。

撒哈拉銀蟻可以用每秒47步跑完85.5公分,這距離達到了牠們體長的108倍之多,如果拿家貓的體型來比喻的話,撒哈拉銀蟻的速度大約是貓貓以時速193公里奔跑。這是因為牠們全速奔跑時,6隻腿會一起騰空再落地,看起來就像是在短暫飛行,在每次換步時,銀蟻每條腿的觸地時間不到7毫秒。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

綠色認證棕櫚油需要支持 監督機構訂新規 企業採購量不足將受罰

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

台塑美國德州廠污染案 台塑同意15.33億和解

摘錄自2019年10月16日自由時報報導

台塑集團德州廠遭居民指控排放塑膠顆粒、污染水資源,聯邦地區法院法官在今年6月裁定,台塑違反廢棄物排放許可證和聯邦淨水法,恐面臨鉅額罰款;台塑當時否認非法傾倒,聲稱排放的塑膠顆粒並未超過許可證允許數量。

《美聯社》15日報導,非營利組織Texas RioGrande Legal Aid(TRLA)週二宣布與台塑達成和解協議,並表示基於雙方同意的判決,台塑同意實行「零排放」並清理既有的汙染;協議還需要經過法官批准,通過後,5000萬美元將在5年內用於改善當地河川的水質。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

象牙海岸法令鬆綁 開放可可業剷平雨林

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

植樹遏止氣候變遷 科學家說成效被高估

摘錄自2019年10月23日中央通訊社馬來西亞報導

科學家警告說,全球大規模植樹遏止氣候變遷風險的可能成效被高估。今年7月,蘇黎世聯邦理工學院(ETH Zurich)柯勞瑟實驗室(Crowther Lab)的研究人員發布研究報告,提出控制氣候變化的最好方法,就是在面積與美國相當的被毀森林重新植樹。

但德國波昂大學(University of Bonn)和位於奈洛比的世界農林複合研究中心「世界混農林業中心」(World Agroforestry Center)的科學家,18日在期刊「科學」(Science)發表回應文指出,在原先研究中可以在土地上種植的樹木數量有限。

波昂大學作物科學與資源保育研究所(Institute of Crop Sciences and Resource Conservation)教授魯德林(Eike Luedeling)表示,植樹造林不應被視為減少使用化石燃料排放的替代方案。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

「人造葉」設備啟蒙自光合作用 可生產乾淨燃料「合成氣」

摘錄自2019年10月22日科技新報報導

科學家開發出太陽能燃料領域一種新設備,可以利用陽光將水、二氧化碳轉化成用來發電的燃料「合成氣」,因轉化過程有如植物光合作用,而被暱稱為「人造葉」。

合成氣是一種利用氣化技術(gasification technology)將煤炭、石油、生質物等含碳原料轉化成一氧化碳與氫氣、然後混合而成的產物,本身可充當燃料氣體,主要用途為發電,也可用來生產藥品、塑膠、肥料等。

而英國劍橋大學團隊花費多年時間設計出人造葉,包含二種先進的鈣鈦礦光吸收劑和一種由鈷製成的分子催化劑,前者作用類似植物中吸收陽光的分子,鈣鈦礦可提供更高的電壓與電流驅動化學反應;後者則是代替鉑或銀,不僅成本較低,催生一氧化碳的表現也比其他催化劑好。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

2020-為什麼換了工作

摘要

經歷了一個特殊的2020上半年,疫情出乎意料的持續了半年之久,還是沒有看到結束的趨勢。雖然外部環境很惡劣,還是做出了個人的重大選擇,換工作。期間糾結了很久,畢竟工作就是生活,換工作就是對未來的期待,對過去的總結,對自己的人生的深度思考。這裏回顧下當時的個人思考,供後續復盤參考。

當前的狀況

2020,本科畢業的第六年,不再像剛畢業那會,覺得換公司是輕而易舉的事,考慮的事情越來越多。
畢業五年開始就越發的焦慮,這是當時的心境
2019年春-當前的困境、
2019秋-走的太久忘記了為什麼出發

總結起來

  1. 在擁抱變化的過程中,沒有匹配上自己的個人目標
  2. 工作多年還依然在糾結擅長和喜歡

老馬說的員工想要離職無非兩個原因

  1. 錢沒給到位
  2. 心委屈了
    分別是物質和精神方面。算是高度概括了。細分起來就是三方面錢、人、事。

錢的計算相對比較複雜了。

  1. 時薪和月薪
    月薪20K 的996,和15K的965相比,時薪還要低不少。但是如何選擇,每個人的情況不一樣,選擇不一樣
  2. 月薪和總包
    互聯網公司的總包除了現金部分還包括股票或者期權。一般分4年歸屬,待滿兩年才能拿到一半。股票的價值不好估算,畢竟二級市場波動很大。對比股票,期權價值就更不好估算了。因為沒上市的公司估值的水分很大,另外不一定能兌現,畢竟上市沒那麼容易。但是機會和風險並存,創業公司會給很多期權來吸引(忽悠)人才加入。你想獲得高額的回報甚至是財務自由,就得冒很大的風險。

所以總收入: 現金 + 風險係數 * 股票/期權價值。 同時考慮你的時間比。

那對於當前的我來說,缺錢,但是不是缺工作跳槽的錢。怎麼說呢,就是生活基本物質得到了保障,但是更高的需求無法滿足,而這些多出來的需求靠換工作是滿足不了。所以錢不是重點考慮的。

互聯網的從業人員的個人素質相對比較高,加上工作專業度比較高,所以都比較簡單,沒那麼多亂七八糟的人情世故需要去處理。所以相處起來還比較愉快。

但是人與人之間是有磁場的,不是每個人都能和你對脾氣、遇到合適的領導,相近的同事也沒那麼容易,另外建立關係也需要時間。這個對於跳槽是減分項。

事包含兩部分,一部分是成就感,一部分個人成長機會。也就是通常說的借人成事、借事修人。

馬斯洛需求理論最高層級就是自我實現。對於互聯網人,每天除了睡覺,70%以上時間都給了工作,那自我實現肯定要在工作中實現。

那對於我個人而已,相對穩定的業務,確定性比較高,沒什麼發展前景的就沒什麼吸引力。希望是有挑戰性的工作,能把事情做成功,並且自己能夠在其中發揮重要的作用。

個人成長其實就是未來。個人成長不是一蹴而就的,短期內不易覺察。但是非常重要,互聯網更新很快,你沒跟上就被淘汰了。對於廣泛流傳的程序員成長路線,3年高工,7年架構,10年外賣。

雖然是調侃,但是也說明了幾點內容

  1. 更新迭代很快的互聯網對大齡人不是很友好,你必須要持續成長。恍惚中我就工作6年,在互聯網行業中都算一個老兵了。
  2. 互聯網人的職業發展與其他行業發展曲線不一樣。一個發展很快,新知識很多的領域,個體是永遠追不上行業的發展水平的。

總結來說,發展是當前跳槽的主要考慮因素。

跳槽的期望

有了換工作的想法,是對現狀的不滿,但是換一個環境並不意味着問題的解決。這也是為什麼很多人經常抱怨公司的不好,但是不挪窩的原因。我們不能寄希望於未知的事情。尤其是從18年底開始互聯網整體再走下坡路,就業環境並不好,加上今年疫情的原因,外部環境更加惡化。

但是大環境不好,不代表個體就不好。不確定性很多不代表沒有確定性的東西。人的一生就是在不斷選擇中度過的,我們必須要了解自己,抓住重點,匹配自己與環境。

那麼我的個人期望是怎麼樣的,新的工作能滿足我的期望嗎?

職業發展

之前迷茫過還要不要做程序員,能不能轉行到產品,諮詢,售前。現在不能說篤定了一直做技術,但是找到了一些發展規律。

  1. 不會再去做偏底層的技術
    以前做過大數據,BI,甚至3年前還拿到過大數據工程師的offer,現在也深入了解了運維、中間件相關的工作內容。徹底打消了偏技術側的技術開發。
  2. 解決問題為驅動
    問題為導向,離業務越近越好。通過技術來驅動業務,非技術手段來解決業務問題。

這個算是近兩年個人的一個不小的突破,排除了哪些事不會去做,哪些事要去嘗試探索。

行業

行業涉及面太廣了,現在toC不好做,巨頭林立把用戶時間都擠佔了。toB 更難了,工具效率型的,國內目前付費意願並不強,能幫助企業帶來收入的才能發展的下去。

個人目前對具體做哪個行業的並沒有那麼執着。更多的期望是能夠以某個點切入到某個行業,然後看到如何通過互聯網技術把問題解決了,形成閉環,把事情做成功了。這種成就感足以讓我興奮。因為這些年,在不同的公司做各種創業項目,都沒有做成功的,都因為各種原因死掉了。正因如此,才知道創業是如何的艱難與不易。

通過自我的剖析和明確當前職業發展目標,在2020年春夏之際,我成功換了工作。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心