新宿市民測定所 發表嬰幼兒奶粉輻射檢查報告

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

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

【其他文章推薦】

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

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

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

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

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

Tesla 挹注擴大 F-貿聯第三季營收季增幅度大

Tesla 日前發表的儲能電池計畫,受到外界關注,法人評估,首波供應商 F-貿聯最快 8 月有機會開始交貨,明年下半年新計畫挹注逐步擴大。法人也預估,該公司本季營收季增個位數、且將挑戰單季新高,第三季起隨 Tesla 新 SUV 車款問世,6 月下旬起零件逐月放量,下季營收季增幅度將拉大,全年營收拚增兩位數。   F-貿聯與 Tesla 的往來關係深厚,早在 Tesla 還未成為全球知名的電動車大廠前,就已相互搭配,因此也成為該客戶的指標核心供應商。一直以來,貿聯較為人熟知是 Tesla 電動車電池管理系統線束的獨家供應商,此次配合與松下合資的超級電池工廠明年下半年量產,電池供應量將大增。   據了解,F-貿聯已是特斯拉除能電池系統 Powerwall 與企業用 Powerpack 計畫的首波零件供應商,供貨品項仍為電池管理系統用線。法人預期,最快 8 月有望正式交貨,雖對今年的營收貢獻不大,但更確認了貿聯在 Tesla 各項計畫共同開發的核心地位,而明年下半年超級電池工廠如果順利量產,該計畫的挹注將明顯增溫。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

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

北汽與Amber Dual合資逾2億人民幣 建馬來西亞電動車工廠

據馬來西亞媒體5月19日報導,北汽集團近日在其北京工廠與Amber Dual有限公司簽訂了合資協議,雙方將投資2-3億元人民幣,在馬來西亞吉打州建立一座新廠,並將耗資約5,000萬元人民幣用於研發,預計於明年7月份開始生產。   此馬來西亞廠將作為北汽在東南亞地區的電動車生產中心。北汽為該工廠設定的電動車年產量目標為2,000到3,000輛,此後將逐漸遞增。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

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

三次握手四次揮手

一直都知道 TCP 建立連接時需要三次握手,釋放連接時需要四次揮手,也大概能說出整個過程,但是一直對其中的設計思想理解不深,停留在“只可意會,不可言傳”的階段。這次寫一篇博客嘗試將其中的思想表達出來。

 

 

 

TCP 建連三次握手

首先解釋一下每個步驟的作用:
1、a 時刻,A 準備就緒,發送 SYN 包給 B,嘗試建立連接
2、b 時刻,B 收到 A 發來的 SYN 包,知道 A 要請求建連,回 SYN ACK 包,告訴 A 自己收到了建連請求,可以建連了
3、c 時刻,A 收到了 B 的回復,知道 B 準備好了,鏈路通暢,可以發送數據了。回  ACK 告知 B 收到了 B 的回復,下面要開始發送該數據了
4、d 時刻,B 收到了 A 的回復,知道 A 接下來要發數據了。至此,AB 雙方都確認整個鏈路已經可靠了,接下來可以發送數據了。

 

為什麼要多次確認呢?為什麼不可以 A 上來就直接發送數據給 B 呢?
這裏首先要明確一點,TCP 是傳輸層的協議,是建立在物理層、數據鏈路層、網絡層之上的協議,而底層的網絡是不可靠的,可能路由出問題,可能網關出問題,可能網線出問題,A 沒法保證自己發出來的消息 B 一定能收到,所以一定要反饋機制,即 ACK,這樣才能在不可靠的網絡層智商構建可靠的傳輸層。

 

類比一下生活中的例子,可以幫助我們理解
示例1,假設我們在火車上打電話,通話質量很差,我們的通話過程可能會是下面這樣:

 

 

AB 雙方首先需要確認彼此都能挺到對方的聲音,也就是保證電話通道是可靠的,之後才會開始說正事。如果一上來就直接說正事,可能 A 說完之後 B 根本就沒有聽到。
實際打電話過程中,如果遇到了斷線的情況,雙方可能需要進行多次“握手”確認。

 

示例2,假設我們給剛認識的人第一次打電話,通話過程可能是下面這樣:

 

 

AB 雙方都要確認對方的身份,也就是保證通話是在跟自己人進行,確保電話通道是可靠的,不是跟騙子通話,然後才會開始說正事。如果一上來沒有確認身份,不能保證通道是跟自己人進行的,那直接說出重要的事,很可能就泄漏了機密。

 

總之,握手過程的最終目的就是保證雙方都準備就緒,通路是可靠的,之後就可以放心的發送重要數據了。

 

那為什麼一定是三次呢,為什麼不是兩次或者四次呢?
先來說一下為什麼不能少。
一次可以嗎?不可以。設想一下,A 對 B 說:我要給你發數據。然後不等 B 的回復,接下來就開始發數據了。這時候根本不能保證 B 已經準備好了,那 A 發出來的數據就沒法保證 B 一定能收到。聯想生活中的場景,你隔着很遠的距離向對方喊話:我要把蘋果扔給你。然後不關心對方有沒有聽到,就直接扔了,那最終的結果通常就是對方接不到蘋果,因為對方可能根本沒有收到消息。
兩次可以嗎?不可以。設想一下,A 對 B 說:我要給你發數據,然後 B 收到消息后給 A 回復:收到,A 在收到 B 的回復后開始發送數據。這時候 A 端是可以準備就緒的,但是 B 端不知道 A 端當前的狀態。因為 B 在收到 A 的消息的時候,可能已經過去了很長時間,B 在回消息的時候,A 可能已經不在線了,此時 B 是不能直接發數據的。如果 A 再給 B 回一個 ACK,B 就可以確認當前鏈路狀態了,這就變成了三次握手。

接下來說一下為什麼不是四次。既然三次已經可以保證建立可靠通信,就不需要額外的一次交互了。

 

下面是幾個生活中相關的示例:

 

 

 

 

 

 

 

 

 

 

 TCP 斷鏈四次揮手
1、a 時刻,A 向 B 發出 FIN 包,表示自己沒有數據要發送了
2、b 時刻,B 收到 FIN 包,回復 FIN ACK,表示收到了 A 的 FIN 包,不會再接收 A 的數據了
3、B 在發完 FIN ACK 后,可能還有數據要發給 A,這個數據是不能停止發送的,有數據還是需要繼續發送
4、d 時刻,B 發完了數據,也發出 FIN 包,告訴 A 自己的數據發完了,不再發送數據了
5、e 時刻,A 收到了 B 的 FIN 包,知道 B 也沒有數據要發送了,回復 FIN ACK。此時,連接可以斷開了

建連只需要交互三次,斷連卻需要四次,這是為什麼呢?其實斷開連接和建立連接還是不一樣的。建連的時候,只要雙方都告知對方自己準備好了就可以,但是斷連的時候,一方提出要斷開連接,不再發數據,另一方不能立即斷開,因為這一方可能還有數據要發送,直到數據全部發送完成后才能確認斷開。

 

下面是幾個生活中相關的示例:

 

 

 

以上是對於三次握手、四次揮手的簡單介紹,裏面沒有更詳細的狀態介紹,之後的博客會介紹,這裏先放兩張圖。
TCP 三次握手

 

 

 

TCP 四次揮手
 

 

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

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

【Spring】內嵌Tomcat&去Xml&調試Mvc

菜瓜:今天聽到個名詞“父子容器”,百度了一下,感覺概念有點空洞,這是什麼核武器?

水稻:你說的是SpringMvc和Spring吧,其實只是一個概念而已,用來將兩個容器做隔離,起到解耦的作用,其中子容器可以拿到父容器的bean,父容器拿不到子容器的。但是SpringBoot出來之後這個概念基本就被淡化掉,沒有太大意義,SpringBoot中只有一個容器了。

菜瓜:能不能給個demo?

水稻:可以。由於現在SpringBoot已經大行其道,Mvc你可能接觸的少,甚至沒接觸過。

  • 早些年啟動一個Mvc項目費老鼻子勁了,要配置各種Xml文件(Web.xml,spring.xml,spring-dispather.xml),然後開發完的項目要打成War包發到Tomcat容器中
  • 現在可以直接引入Tomcat包,用main方法直接調起。為了調試方便,我就演示一個Pom引入Tomcat的例子
  • ①啟動類
  • package com.vip.qc.mvc;
    
    import org.apache.catalina.Context;
    import org.apache.catalina.LifecycleException;
    import org.apache.catalina.LifecycleListener;
    import org.apache.catalina.startup.Tomcat;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    /**
     * 參考: * https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet
     * <p>
     * 嵌入tomcat,由Tomcat發起對Spring容器的初始化調用過程
     * <p>
     * - 啟動過程
     * * - Servlet規範,Servlet容器在啟動之後會SPI加載META-INF/services目錄下的實現類並調用其onStartup方法
     * * - Spring遵循規範實現了ServletContainerInitializer接口。該接口在執行時會收集WebApplicationInitializer接口實現類並循環調用其onStartup方法
     * * - 其中AbstractDispatcherServletInitializer
     * * * - 將spring上下文放入ContextLoaderListener監聽器,該監聽會發起對refresh方法的調用
     * * * - 註冊dispatcherServlet,後續會由tomcat調用HttpServletBean的init方法,完成子容器的refresh調用
     * *
     *
     * @author QuCheng on 2020/6/28.
     */
    public class SpringWebStart {
    
        public static void main(String[] args) {
            Tomcat tomcat = new Tomcat();
            try {
                // 此處需要取一個目錄
                Context context = tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
                context.addLifecycleListener((LifecycleListener) Class.forName(tomcat.getHost().getConfigClass()).newInstance());
                tomcat.setPort(8081);
                tomcat.start();
                tomcat.getServer().await();
            } catch (LifecycleException | ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                e.printStackTrace();
            }
        }
    
    
        static class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
            private final static String PACKAGE_PATH = "com.vip.qc.mvc";
    
            @Override
            protected String[] getServletMappings() {
                return new String[]{"/"};
            }
    
            @Override
            protected Class<?>[] getRootConfigClasses() {
                // spring 父容器
                return new Class[]{AppConfig.class};
            }
    
            @Override
            protected Class<?>[] getServletConfigClasses() {
                // servlet 子容器
                return new Class[]{ServletConfig.class};
            }
    
            @ComponentScan(value = PACKAGE_PATH,
                    excludeFilters = {
                            @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class),
                            // 避免掃描到加了註解(@Configuration)的子容器
                            @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ServletConfig.class)})
            static class AppConfig {
            }
    
            @ComponentScan(value = PACKAGE_PATH)
            static class ServletConfig {
            }
        }
    }
  • ②Controller&Service
  • package com.vip.qc.mvc.controller;
    
    import com.vip.qc.mvc.service.ServiceChild;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.annotation.Resource;
    
    /**
     * @author QuCheng on 2020/6/28.
     */
    @Controller
    public class ControllerT implements ApplicationContextAware {
    
        @Resource
        private ServiceChild child;
    
        @RequestMapping("/hello")
        @ResponseBody
        public String containter() {
            child.getParent();
            System.out.println("parentContainer");
            return "containter";
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            System.out.println("子容器" + applicationContext);
            System.out.println("子容器中獲取父容器bean" + applicationContext.getBean(ServiceChild.class));
        }
    }
    
    
    package com.vip.qc.mvc.service;
    
    import com.vip.qc.mvc.controller.ControllerT;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.NoSuchBeanDefinitionException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Service;
    
    /**
     * @author QuCheng on 2020/6/28.
     */
    @Service
    public class ServiceChild implements ApplicationContextAware {
    
        //    @Resource
        private ControllerT controllerT;
    
        public void getParent() {
    
            System.out.println(controllerT);
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            System.out.println("父容器" + applicationContext);
            try {
                System.out.println("父容器中獲取子容器bean" + applicationContext.getBean(ControllerT.class));
            } catch (NoSuchBeanDefinitionException e) {
                System.out.println("找不到子容器的bean");
            }
        }
    }

    // 調用
    SpringWebStart的main方法啟動-會有如下打印
    父容器Root WebApplicationContext, started on Sun Jun 28 22:03:52 CST 2020
    找不到子容器的bean
    子容器WebApplicationContext for namespace 'dispatcher-servlet', started on Sun Jun 28 22:03:58 CST 2020, parent: Root WebApplicationContext
    子容器中獲取父容器beancom.vip.qc.mvc.service.ServiceChild@4acfc43a
    
    
  • Demo比較簡單,不過也能反映父子容器的關係

菜瓜:嗯,效果看到了,能不能講一下啟動過程

水稻:稍等,我去下載源碼。上面代碼演示中已經提前說明了,父子容器的加載是Tomcat依據Servlet規範發起調用完成的

  • spring-web源碼包的/META-INF中能找到SPI的實際加載類SpringServletContainerInitializer#onStartup()方法會搜集實現WebApplicationInitializer接口的類,並調用其onStartup方法
  • 上面MyWebApplicationInitializer啟動類是WebApplicationInitializer的子類,未實現onStartup,實際調用的是其抽象父類AbstractDispatcherServletInitializer的方法。跟進去 
  • @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
       //① 創建Spring父容器上下文-對象放入ContextLoadListener,後續調起完成初始化,
       super.onStartup(servletContext);
       //② 創建DispatcherServlet對象,後續會由tomcat調用其init方法,完成子容器的初始化工作
       registerDispatcherServlet(servletContext);
    }
    
    // ①進來
    protected void registerContextLoaderListener(ServletContext servletContext) {
        // 此處會回調我們啟動類的getRootConfigClasses()方法 - 父容器配置
        WebApplicationContext rootAppContext = createRootApplicationContext();
        if (rootAppContext != null) {
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            istener.setContextInitializers(getRootApplicationContextInitializers());
            servletContext.addListener(listener);
        }
        else {
            logger.debug("No ContextLoaderListener registered, as " +
                  "createRootApplicationContext() did not return an application context");
        }
    }
    
    // ②進來
    protected void registerDispatcherServlet(ServletContext servletContext) {
            。。。
        // 此處會回調我們啟動類的getServletConfigClasses()方法 - 子容器配置
        WebApplicationContext servletAppContext = createServletApplicationContext();
            。。。
        // 初始化的dispatcherServlet,會加入Tomcat容器中-後續調用
        // FrameworkServlet#initServletBean()會完成上下文初始化工作
        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
            。。。
    }

菜瓜:這樣容器就可以用了嗎?

水稻:是的,這樣就可以直接在瀏覽器上面訪問http://localhost:8081/hello,不過這是一個最簡陋的web項目

菜瓜:懂了,最簡陋是什麼意思

水稻:如果我們想加一些常見的Web功能,譬如說攔截器,過濾器啥的。可以通過@EnableWebMvc註解自定義一些功能

  • package com.vip.qc.mvc;
    
    import com.vip.qc.mvc.interceptor.MyInterceptor1;
    import com.vip.qc.mvc.interceptor.MyInterceptor2;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    import javax.annotation.Resource;
    
    /**
     * @author QuCheng on 2020/6/28.
     */
    @Configuration
    @EnableWebMvc
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Resource
        private MyInterceptor1 interceptor1;
        @Resource
        private MyInterceptor2 interceptor2;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(interceptor1).addPathPatterns("/interceptor/**");
            registry.addInterceptor(interceptor2).addPathPatterns("/interceptor/**");
        }
    }
    
    
    
    package com.vip.qc.mvc.interceptor;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @author QuCheng on 2020/6/28.
     */
    @Configuration
    public class MyInterceptor1 implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("嘻嘻 我是攔截器1 pre");
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("嘻嘻 我是攔截器1 post");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("嘻嘻 我是攔截器1 after");
        }
    }
    
    
    package com.vip.qc.mvc.interceptor;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @author QuCheng on 2020/6/28.
     */
    @Configuration
    public class MyInterceptor2 implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("嘻嘻 我是攔截器2 pre");
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("嘻嘻 我是攔截器2 post");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("嘻嘻 我是攔截器2 after");
        }
    
    } 

菜瓜:我知道,這裏還有個Mvc請求調用流程和這個攔截器有關。而且這個攔截器不是MethodInterceptor(切面)

水稻:沒錯,說到這裏順便複習一下Mvc的請求過程

  • 請求最開始都是通過Tomcat容器轉發過來的,調用鏈:HttpServlet#service() -> FrameworkServlet#processRequest() -> DispatcherServlet#doDispather()
  •  1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
     2     。。。
     3   processedRequest = checkMultipart(request);
     4   multipartRequestParsed = (processedRequest != request);
     5   // 1.返回一個持有methodHandler(按照URL匹配得出的被調用bean對象以及目標方法)調用鏈(攔截器鏈)對象
     6   mappedHandler = getHandler(processedRequest);
     7   。。。
     8   // 2.按照我們現在寫代碼的方式,只會用到HandlerMethod,其他三種基本不會用
     9   HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    10   。。。
    11   // 3.前置過濾器 - 順序調用
    12   if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    13       return;
    14   }
    15   // 4.Actually invoke the handler.
    16   mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    17   。。。
    18     applyDefaultViewName(processedRequest, mv);
    19   // 5.後置過濾器 - 逆序調用
    20   mappedHandler.applyPostHandle(processedRequest, response, mv);
    21   。。。
    22   // 6.處理試圖 - 內部render
    23   processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    24   }
    25   catch (Exception ex) {
    26      // 異常處理
    27      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    28   }
    29       // 異常處理
    30   catch (Throwable err) {
    31      triggerAfterCompletion(processedRequest, response, mappedHandler,
    32                     new NestedServletException("Handler processing failed", err));
    33   }
    34     。。。

     

 菜瓜:這個之前看過不少,百度一大堆,不過還是源碼親切

 

總結:

  • 目前基本互聯網項目都是SpringBoot起手了,再難遇到SpringMvc的項目,不過熟悉該流程有利於我們更加深刻的理解Ioc容器
  • Mvc攔截器鏈也是日常開發中會用到的功能,順便熟悉一下請求的執行過程

 

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

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

智慧充電管理軟體真的有效,加州電動交通車隊省下 40% 電費

隨著綠能推展,全球無數新創事業應運而生,宣稱智慧軟體能在能源多元化時代為顧客省錢,也包括電動車隊的智慧充電管理系統,這些系統真能達到宣稱的效益嗎?如今第一波實際使用的成果已逐漸顯現。新創事業 Amply Power 於 2020 年 4 月發表系統應用於北加州康特拉科斯塔郡 Tri Delta 交通車隊電動車的成效,自 2020 年初以來,為車隊節省了 40% 電費。

隨著都市市區環保意識上升,以及各國推動減碳,加上電池降價使得電動車購置成本降低,越來越多公車與交通車隊改用電動車,目前全球有超過 200 家交通車隊採用電動車,並預計到 2025 年電動巴士將占全球交通車隊 30%。

Tri Delta 早在 2018 年就已經開始加入潮流,當時只先測試 4 輛電動車,2 輛來自比亞迪、2 輛來自加州電動巴士廠 Proterra,即使只是先測試 4 輛,對車隊的後勤作業就造成相當大的改變。少了 4 輛車需要柴油與一般引擎車的維修,但多出與電力公司交涉、升級變電器、安裝高壓電力裝置等任務,僅 4 輛車就導致最高電力需求負載高達 300 千瓦,這下突然得處理很多過去不曾想過的電力節費問題。

於是 2019 年底 Tri Delta 決定採用 Amply Power 的智慧充電管理軟體,由軟體管理充電時間,挑選電費較低的離峰時間充電,以節省電費,但又同時確保要用車的時候電力有充飽,車隊後勤人員不再需要思考何時插上充電座比較省錢,讓軟體決定就好了。此外,改採電動車之後,最大的困擾就是一旦充電沒有插好,等到隔天要用車,車子沒電才發現,就會造成調度嚴重問題,使用充電管理軟體後,充電若沒插好,軟體會發出警告,一勞永逸解決了這個防呆問題。

充電管理結果能否擴大適用還待驗證

使用充電管理軟體的期間,Tri Delta 的電力公司太平洋瓦電(PG&E)調整過尖峰用電時間表,Tri Delta 本身用電變化也讓所屬費率區間有變動,過去這些都會造成負責充電的員工傷透腦筋,每週的最佳充電時間都不同,一不小心就讓公司承受高額電費,這些變動更顯出充電管理軟體的重要性,充電管理軟體會自動依據電力公司的時間表與公式調整,算出最划算的充電時間。

經過幾個月使用下來,Tri Delta 節省了高達 40% 電費支出,這還只是第一季,進入夏季後,尖峰用電費率落差更大,會讓充電管理的效益更明顯。

不過,這僅是 4 輛電動車的充電管理結果,能否擴大適用到上百台規模的大車隊,尚需進一步驗證。不過越大的車隊,原理上來說,充電管理能達到的效益會更顯著,不僅節省電費更多,軟體最佳化分配車輛充電的時間,更可以讓硬體投資也跟著減少,例如設置較少充電座,或讓電力設備不需太大規模升級,節省建設成本與工程時間。

Amply Power 此實證案例,不僅證明充電管理的重要性,為許多管理軟體新創事業開創市場,另一方面,也同時為整個電動車產業打了一劑定心針:電動巴士車隊雖然成本較高,但可仰賴電費比燃料費用便宜來達成經濟效益,如今這商業模式面臨挑戰,因油價在全球新冠病毒疫情影響下降到低點,電動車的費用優勢可能消失,但是,若充電管理能省下 40% 電費,那麼,電動車隊尚可取回一部分競爭優勢。

(合作媒體:。首圖來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

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

急轉彎!特斯拉取消提前復工,股價由漲轉跌

先前消息傳出,電動車大廠特斯拉(Tesla)要求員工於 29 日返回工作崗位,以便讓加州佛利蒙(Fremont)組裝廠恢復生產。但隨著該廠所在的舊金山灣區六郡宣佈延長居家禁令,特斯拉政策急轉彎,已通知員工無需提前復工。

CNBC 報導,根據特斯拉的內部信件,24 日和週末,特斯拉管理層要求數十名員工於 29 日重返工作崗位,讓佛利蒙廠產線重啟運作。不過,由於舊金山灣區六郡(舊金山、聖克拉拉、聖馬刁、馬林、康特拉哥斯,以及佛利蒙廠所在的阿拉米達郡)27 日宣佈,將「就地避難」(shelter-in-place)令的期限由 5 月 4 日延長至 5 月底,特斯拉隨後也取消本週的復工計畫。

特斯拉人力資源部週一發送內部訊息向員工表示:「按照執行領導團隊的指示,我們將不會在 4 月 29 日星期三恢復工作。請忽略關於本週復工的所有訊息和指示。」

報導指出,特斯拉佛利蒙廠生產 Model S 和 Model X 車型,以及較新的 Model 3 和 Model Y 車型,銷往北美和歐洲市場。位於中國上海的組裝廠,先前也因疫情短暫關閉約兩週,但在當地政府的協助下迅速恢復運作,目前每週營運 6 天。

在佛利蒙廠有望提前復工的利多消息帶動下,27 日特斯拉股價大漲 10.15% 收 798.75 美元,但取消復工的消息公佈後,盤後股價挫跌 2.10% 至 782.00 美元。今年以來,特斯拉股價累計飆漲 90.98%。

《華爾街日報》先前報導,根據特斯拉 4 月 2 日資料,2020 年第一季交車數達 88,400 輛,較去年同期成長 40%,雖然疫情導致整體車市市況不振,但特斯拉仍維持原先銷售目標。

今年初,特斯拉執行長馬斯克(Elon Musk)預估,特斯拉今年電動車銷量將維持強勁成長,比去年至少成長 36%,全球總交車數可望「輕鬆突破」(comfortably exceed)50 萬輛。

特斯拉預計 29 日發表 2020 年第一季財報。

(本文內文由  授權使用;首圖來源: CC BY 2.0)

延伸閱讀:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

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

pythonic context manager知多少

Context Managers 是我最喜歡的 python feature 之一,在恰當的時機使用 context manager 使代碼更加簡潔、清晰,更加安全,復用性更好,更加 pythonic。本文簡單介紹一下其使用方法以及常見使用場景。

本文地址:https://www.cnblogs.com/xybaby/p/13202496.html

with statement and context manager

Python’s with statement supports the concept of a runtime context defined by a context manager

new statement “with” to the Python language to make it possible to factor out standard uses of try/finally statements.

在 pep0343 中,通過引入 context manager protocol 來支持 With statement , context manager 是用來管理 context(上下文)的,即保證程序要保持一種特定的狀態 — 無論是否發生異常。可以說,context manager 簡化了對 try-finally 的使用,而且更加安全,更加便於使用。

Transforming Code into Beautiful, Idiomatic Python 中,指出了 context manager 的最顯著的優點:

  • Helps separate business logic from administrative logic
  • Clean, beautiful tools for factoring code and improving code reuse

最廣為人知的例子,就是通過 with statement 來讀寫文件,代碼如下:

with open('test.txt') as f:
    contect = f.read()
    handle_content(content)

上面的代碼幾乎等價於

f = open('test.txt') 
try:
    contect = f.read()
    handle_content(content)
finally:
    f.close()

注意,上面的finally的作用就是保證file.close一定會被調用,也就是資源一定會釋放。不過,很多時候,都會忘了去寫這個finally,而 with statement 就徹底避免了這個問題。

從上述兩段代碼也可以看出,with statement 更加簡潔,而且將核心的業務邏輯(從文件中讀取、處理數據)與其他邏輯(打開、關係文件)相分離,可讀性更強。

實現context manager protocol

一個類只要定義了__enter____exit__方法就實現了context manager 協議

object.__enter__(self)
Enter the runtime context related to this object. The with statement will bind this method’s return value to the target(s) specified in the as clause of the statement, if any.

object.__exit__(self, exc_type, exc_value, traceback)
Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.

If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.

Note that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.

__enter__方法在進入這個 context 的時候調用,返回值賦值給 with as X 中的 X

__exit__方法在退出 context 的時候調用,如果沒有異常,后三個參數為 None。如果返回值為 True,則Suppress Exception,所以除非特殊情況都應返回 False。另外注意, __exit__方法本身不應該拋出異常。

例子:BlockGuard

在看c++代碼(如mongodb源碼)的時候,經常看見其用 RAII 實現BlockGuard, 用以保證在離開 Block 的時候執行某些動作,同時,也提供手段來取消執行。

下面用python實現一下:

class BlockGuard(object):
	def __init__(self, fn, *args, **kwargs):
		self._fn = fn
		self._args = args
		self._kwargs = kwargs
		self._canceled = False

	def __enter__(self):
		return self

	def __exit__(self, exc_type, exc_value, traceback):
		if not self._canceled:
			self._fn(*self._args, **self._kwargs)
		self._fn = None
		self._args = None
		self._kwargs = None
		return False

	def cancel(self):
		self._canceled = True


def foo():
	print 'sth should be called'


def test_BlockGuard(cancel_guard):
	print 'test_BlockGuard'
	with BlockGuard(foo) as guard:
		if cancel_guard:
			guard.cancel()
	print 'test_BlockGuard  finish'

用yield實現context manager

標準庫 contextlib 中提供了一些方法,能夠簡化我們使用 context manager,如 contextlib.contextmanager(func) 使我們
無需再去實現一個包含__enter__ __exit__方法的類。

The function being decorated must return a generator-iterator when called. This iterator must yield exactly one value, which will be bound to the targets in the with statement’s as clause, if any.

例子如下:

from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwds):
    # Code to acquire resource, e.g.:
    resource = acquire_resource(*args, **kwds)
    try:
        yield resource
    finally:
        # Code to release resource, e.g.:
        release_resource(resource)

>>> with managed_resource(timeout=3600) as resource:
...     # Resource is released at the end of this block,
...     # even if code in the block raises an exception

需要注意的是:

  • 一定要寫 try finally,才能保證release_resource邏輯一定被調用
  • 除非特殊情況,不再 catch exception,這就跟 __exit__ 一般不返回True一樣

例子: no_throw

這是業務開發中的一個需求, 比如觀察者模式,不希望因為其中一個觀察者出了 trace 就影響後續的觀察者,就可以這樣做:

from contextlib import contextmanager

@contextmanager
def no_throw(*exceptions):
	try:
		yield
	except exceptions:
		pass

def notify_observers(seq):
	for fn in [sum, len, max, min]:
		with no_throw(Exception):
			print "%s result %s" % (fn.__name__, fn(seq))

if __name__ == '__main__':
	notify_observers([])

在python 3.x 的 contexlib 中,就提供了一個contextlib.suppress(*exceptions), 實現了同樣的效果。

context manager 應用場景

context manager 誕生的初衷就在於簡化 try-finally,因此就適合應用於在需要 finally 的地方,也就是需要清理的地方,比如

  • 保證資源的安全釋放,如 file、lock、semaphore、network connection 等
  • 臨時操作的復原,如果一段邏輯有 setup、prepare,那麼就會對應 cleanup、teardown。

對於第一種情況,網絡連接釋放的例子,後面會結合 pymongo 的代碼展示。

在這裏先來看看第二種用途:保證代碼在一個臨時的、特殊的上下文(context)中執行,且在執行結束之後恢復到之前的上下文環境。

改變工作目錄

from contextlib import contextmanager
import os

@contextmanager
def working_directory(path):
    current_dir = os.getcwd()
    os.chdir(path)
    try:
        yield
    finally:
        os.chdir(current_dir)

with working_directory("data/stuff"):
    pass

臨時文件、文件夾

很多時候會產生一堆臨時文件,比如build的中間狀態,這些臨時文件都需要在結束之後清除。

from tempfile import mkdtemp
from shutil import rmtree

@contextmanager
def temporary_dir(*args, **kwds):
    name = mkdtemp(*args, **kwds)
    try:
        yield name
    finally:
        shutil.rmtree(name)

with temporary_dir() as dirname:
    pass

重定向標準輸出、標準錯誤

@contextmanager
def redirect_stdout(fileobj):
    oldstdout = sys.stdout
    sys.stdout = fileobj
    try:
        yield fieldobj
    finally:
        sys.stdout = oldstdout

在 python3.x 中,已經提供了 contextlib.redirect_stdout contextlib.redirect_stderr 實現上述功能

調整logging level

這個在查問題的適合非常有用,一般生產環境不會輸出 debug level 的日誌,但如果出了問題,可以臨時對某些制定的函數調用輸出debug 日誌

from contextlib import contextmanager
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

ch = logging.StreamHandler()
ch.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(ch)


@contextmanager
def change_log_level(level):
	old_level = logger.getEffectiveLevel()
	try:
		logger.setLevel(level)
		yield
	finally:
		logger.setLevel(old_level)


def test_logging():
	logger.debug("this is a debug message")
	logger.info("this is a info message")
	logger.warn("this is a warning message")

with change_log_level(logging.DEBUG):
	test_logging()

pymongo中的context manager使用

在 pymongo 中,封裝了好幾個 context manager,用以

  • 管理 semaphore
  • 管理 connection
  • 資源清理

而且,在 pymongo 中,給出了嵌套使用 context manager 的好例子,用來保證 socket 在使用完之後一定返回連接池(pool)。

# server.py
@contextlib.contextmanager
def get_socket(self, all_credentials, checkout=False):
    with self.pool.get_socket(all_credentials, checkout) as sock_info:
        yield sock_info
        
# pool.py
@contextlib.contextmanager
def get_socket(self, all_credentials, checkout=False):
    sock_info = self._get_socket_no_auth()
    try:
        sock_info.check_auth(all_credentials)
        yield sock_info
    except:
        # Exception in caller. Decrement semaphore.
        self.return_socket(sock_info)
        raise
    else:
        if not checkout:
            self.return_socket(sock_info)

可以看到,server.get_socket 調用了 pool.get_socket, 使用 server.get_socket 的代碼完全不了解、也完全不用關心 socket 的釋放細節,如果把 try-except-finally-else 的邏輯移到所有使用socket的地方,代碼就會很醜、很臃腫。

比如,在mongo_client 中需要使用到 socket:

with server.get_socket(all_credentials) as sock_info:
    sock_info.authenticate(credentials)

references

With statement

Context Managers

contextlib

what-is-the-python-with-statement-designed-for

Transforming Code into Beautiful, Idiomatic Python

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

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

上周熱點回顧(6.22-6.28)

熱點隨筆:

· 程序員敲代碼時耳機里聽的到底是什麼? (風的姿態)
· CPU明明8個核,網卡為啥拚命折騰一號核? (軒轅之風)
· 手把手教你基於SqlSugar4編寫一個可視化代碼生成器(生成實體,以SqlServer為例,文末附源碼) (熊澤-學習中的苦與樂)
· 在運行時生成C# .NET類 (芝麻麻雀)
· 因為我的一個低級錯誤,生產數據庫崩潰了將近半個小時 (鄙人薛某)
· C# 人臉識別庫 (View12138)
· 基於領域驅動設計(DDD)超輕量級快速開發架構 (阿新)
· .Net Core 中GC的工作原理 (她微笑的臉)
· 關於技術文章“標題黨”一事我想說兩句 (精緻碼農)
· 【故障公告】阿里雲 RDS 實例 CPU 100% 故障引發全站無法正常訪問 (博客園團隊)
· 思考:如何保證服務穩定性? (老_張)
· 只看到了別人28歲從字節跳動退休,背後的期權知識你知道嗎? (四猿外)

熱點新聞:

· 瘋王,任正非!
· VSCode彩虹屁插件:釘宮理惠,英雄聯盟版現已生成,你Pick哪一個?
· 全國首創!廣東人坐火車就像坐地鐵一樣方便了:無需提前買票
· 95后快遞小哥獲評“高層次人才”:杭州買房享受百萬元補貼
· 二線手機廠商墜落簡史:鎚子、魅族、金立已成過客
· 外賣員確診背後:年近50 每天接老婆下班 工作14小時
· 歷時26年!中國終於有了自己的全球導航系統
· 看!北斗三號最後一顆組網衛星在太空張開“翅膀” 畫面燃了
· 攻克地獄級難度!川藏鐵路拉林段120座橋樑主體工程全部完工
· 作家王小山控訴攜程欠錢不還:願意下跪懇請梁建章退租
· 知乎熱議:替代Matlab的國產軟件出現 半年內實現Matlab功能的70%
· 這個比QQ空間還古老的網站 是多少女孩的精神家園?

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

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

重學 Java 設計模式:實戰備忘錄模式「模擬互聯網系統上線過程中,配置文件回滾場景」

作者:小傅哥
博客:https://bugstack.cn – 原創系列專題文章

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

實現不了是研發的借口?

實現不了,有時候是功能複雜度較高難以實現,有時候是工期較短實現不完。而編碼的行為又是一個不太好量化的過程,同樣一個功能每個人的實現方式不一樣,遇到開發問題解決問題的速度也不一樣。除此之外還很不好給產品解釋具體為什麼要這個工期時間,這就像蓋樓的圖紙最終要多少水泥砂漿一樣。那麼這時研發會盡可能的去通過一些經驗,制定流程規範、設計、開發、評審等,確定一個可以完成的時間範圍,又避免風險的時間點后。再被壓縮,往往會出一些矛盾點,能壓縮要解釋為什麼之前要那麼多時間,不能壓縮又有各方不斷施加的壓力。因此有時候不一定是借口,是要考慮如何讓整個團隊健康的發展。

鼓勵有時比壓力要重要!

在學習的過程中,很多時候我們聽到的都是,你要怎樣,怎樣,你瞧瞧誰誰誰,哪怕今天聽不到這樣的聲音了,但因為曾經反覆聽到過而導致內心抗拒。雖然也知道自己要去學,但是很難堅持,學着學着就沒有了方向,看到還有那麼多不會的就更慌了,以至於最後心態崩了,更不願意學。其實程序員的壓力並不小,想成長几乎是需要一直的學習,就像似乎再也不敢說精通java了一樣,知識量實在是隨着學習的深入,越來越深,越來越廣。所以需要,開心學習,快樂成長!

臨陣的你好像一直很着急!

經常的聽到;老師明天就要了你幫我弄弄吧你給我寫一下完事我就學這次着急現在這不是沒時間學嗎快給我看看。其實看到的類似的還有很多,很納悶你的着急怎麼來的,不太可能,人在家中坐,禍從天上落。老師怎麼就那個時間找你了,老闆怎麼就今天管你要了,還不是日積月累你沒有學習,臨時抱佛腳亂着急!即使後來真的有人幫你了,但最好不要放鬆,要儘快學會,躲得過初一還有初二呢!

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程一個,可以通過關注公眾號bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-17-00 開發配置文件備忘錄

三、備忘錄模式介紹

備忘錄模式是以可以恢復或者說回滾,配置、版本、悔棋為核心功能的設計模式,而這種設計模式屬於行為模式。在功能實現上是以不破壞原對象為基礎增加備忘錄操作類,記錄原對象的行為從而實現備忘錄模式。

這個設計在我們平常的生活或者開發中也是比較常見的,比如:後悔葯、孟婆湯(一下回滾到0),IDEA編輯和撤銷、小霸王遊戲機存檔。當然還有我們非常常見的Photoshop,如下;

四、案例場景模擬

在本案例中我們模擬系統在發布上線的過程中記錄線上配置文件用於緊急回滾

在大型互聯網公司系統的發布上線一定是易用、安全、可處理緊急狀況的,同時為了可以隔離線上和本地環境,一般會把配置文件抽取出來放到線上,避免有人誤操作導致本地的配置內容發布出去。同時線上的配置文件也會在每次變更的時候進行記錄,包括;版本號、時間、MD5、內容信息和操作人。

在後續上線時如果發現緊急問題,系統就會需要回滾操作,如果執行回滾那麼也可以設置配置文件是否回滾。因為每一個版本的系統可能會隨着帶着一些配置文件的信息,這個時候就可以很方便的讓系統與配置文件一起回滾操作。

我們接下來就使用備忘錄模式,模擬如何記錄配置文件信息。實際的使用過程中還會將信息存放到庫中進行保存,這裏暫時只是使用內存記錄。

五、備忘錄模式記錄配置文件版本信息

備忘錄的設計模式實現方式,重點在於不更改原有類的基礎上,增加備忘錄類存放記錄。可能平時雖然不一定非得按照這個設計模式的代碼結構來實現自己的需求,但是對於功能上可能也完成過類似的功能,記錄系統的信息。

除了現在的這個案例外,還可以是運營人員在後台erp創建活動對信息的記錄,方便運營人員可以上下修改自己的版本,而不至於因為誤操作而丟失信息。

1. 工程結構

itstack-demo-design-17-00
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── Admin.java
    │           ├── ConfigFile.java
    │           ├── ConfigMemento.java
    │           └── ConfigOriginator.java
    └── test
        └── java
            └── org.itstack.demo.design.test
                └── ApiTest.java

備忘錄模式模型結構

  • 以上是工程結構的一個類圖,其實相對來說並不複雜,除了原有的配置類(ConfigFile)以外,只新增加了三個類。
  • ConfigMemento:備忘錄類,相當於是對原有配置類的擴展
  • ConfigOriginator:記錄者類,獲取和返回備忘錄類對象信息
  • Admin:管理員類,用於操作記錄備忘信息,比如你一些列的順序執行了什麼或者某個版本下的內容信息

2. 代碼實現

2.1 配置信息類

public class ConfigFile {

    private String versionNo; // 版本號
    private String content;   // 內容
    private Date dateTime;    // 時間
    private String operator;  // 操作人
    
    // ...get/set
}
  • 配置類可以是任何形式的,這裏只是簡單的描述了一個基本的配置內容信息。

2.2 備忘錄類

public class ConfigMemento {

    private ConfigFile configFile;

    public ConfigMemento(ConfigFile configFile) {
        this.configFile = configFile;
    }

    public ConfigFile getConfigFile() {
        return configFile;
    }

    public void setConfigFile(ConfigFile configFile) {
        this.configFile = configFile;
    }
    
}
  • 備忘錄是對原有配置類的擴展,可以設置和獲取配置信息。

2.3 記錄者類

public class ConfigOriginator {

    private ConfigFile configFile;

    public ConfigFile getConfigFile() {
        return configFile;
    }

    public void setConfigFile(ConfigFile configFile) {
        this.configFile = configFile;
    }

    public ConfigMemento saveMemento(){
        return new ConfigMemento(configFile);
    }

    public void getMemento(ConfigMemento memento){
        this.configFile = memento.getConfigFile();
    }

}
  • 記錄者類除了對ConfigFile配置類增加了獲取和設置方法外,還增加了保存saveMemento()、獲取getMemento(ConfigMemento memento)
  • saveMemento:保存備忘錄的時候會創建一個備忘錄信息,並返回回去,交給管理者處理。
  • getMemento:獲取的之後並不是直接返回,而是把備忘錄的信息交給現在的配置文件this.configFile,這部分需要注意。

2.4 管理員類

public class Admin {

    private int cursorIdx = 0;
    private List<ConfigMemento> mementoList = new ArrayList<ConfigMemento>();
    private Map<String, ConfigMemento> mementoMap = new ConcurrentHashMap<String, ConfigMemento>();

    public void append(ConfigMemento memento) {
        mementoList.add(memento);
        mementoMap.put(memento.getConfigFile().getVersionNo(), memento);
        cursorIdx++;
    }

    public ConfigMemento undo() {
        if (--cursorIdx <= 0) return mementoList.get(0);
        return mementoList.get(cursorIdx);
    }

    public ConfigMemento redo() {
        if (++cursorIdx > mementoList.size()) return mementoList.get(mementoList.size() - 1);
        return mementoList.get(cursorIdx);
    }

    public ConfigMemento get(String versionNo){
        return mementoMap.get(versionNo);
    }

}
  • 在這個類中主要實現的核心功能就是記錄配置文件信息,也就是備忘錄的效果,之後提供可以回滾和獲取的方法,拿到備忘錄的具體內容。
  • 同時這裏設置了兩個數據結構來存放備忘錄,實際使用中可以按需設置。List<ConfigMemento>Map<String, ConfigMemento>
  • 最後是提供的備忘錄操作方法;存放(append)、回滾(undo)、返回(redo)、定向獲取(get),這樣四個操作方法。

3. 測試驗證

3.1 編寫測試類

@Test
public void test() {
    Admin admin = new Admin();
    ConfigOriginator configOriginator = new ConfigOriginator();
    configOriginator.setConfigFile(new ConfigFile("1000001", "配置內容A=哈哈", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置
    configOriginator.setConfigFile(new ConfigFile("1000002", "配置內容A=嘻嘻", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置
    configOriginator.setConfigFile(new ConfigFile("1000003", "配置內容A=么么", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置
    configOriginator.setConfigFile(new ConfigFile("1000004", "配置內容A=嘿嘿", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置  

    // 歷史配置(回滾)
    configOriginator.getMemento(admin.undo());
    logger.info("歷史配置(回滾)undo:{}", JSON.toJSONString(configOriginator.getConfigFile()));  

    // 歷史配置(回滾)
    configOriginator.getMemento(admin.undo());
    logger.info("歷史配置(回滾)undo:{}", JSON.toJSONString(configOriginator.getConfigFile()));  

    // 歷史配置(前進)
    configOriginator.getMemento(admin.redo());
    logger.info("歷史配置(前進)redo:{}", JSON.toJSONString(configOriginator.getConfigFile()));   

    // 歷史配置(獲取)
    configOriginator.getMemento(admin.get("1000002"));
    logger.info("歷史配置(獲取)get:{}", JSON.toJSONString(configOriginator.getConfigFile()));
}
  • 這個設計模式的學習有一部分重點是體現在了單元測試類上,這裏包括了四次的信息存儲和備忘錄歷史配置操作。
  • 通過上面添加了四次配置后,下面分別進行操作是;回滾1次再回滾1次之後向前進1次最後是獲取指定的版本配置。具體的效果可以參考測試結果。

3.2 測試結果

23:12:09.512 [main] INFO  org.itstack.demo.design.test.ApiTest - 歷史配置(回滾)undo:{"content":"配置內容A=嘿嘿","dateTime":159209829432,"operator":"小傅哥","versionNo":"1000004"}
23:12:09.514 [main] INFO  org.itstack.demo.design.test.ApiTest - 歷史配置(回滾)undo:{"content":"配置內容A=么么","dateTime":159209829432,"operator":"小傅哥","versionNo":"1000003"}
23:12:09.514 [main] INFO  org.itstack.demo.design.test.ApiTest - 歷史配置(前進)redo:{"content":"配置內容A=嘿嘿","dateTime":159209829432,"operator":"小傅哥","versionNo":"1000004"}
23:12:09.514 [main] INFO  org.itstack.demo.design.test.ApiTest - 歷史配置(獲取)get:{"content":"配置內容A=嘻嘻","dateTime":159320989432,"operator":"小傅哥","versionNo":"1000002"}

Process finished with exit code 0
  • 從測試效果上可以看到,歷史配置按照我們的指令進行了回滾和前進,以及最終通過指定的版本進行獲取,符合預期結果。

六、總結

  • 此種設計模式的方式可以滿足在不破壞原有屬性類的基礎上,擴充了備忘錄的功能。雖然和我們平時使用的思路是一樣的,但在具體實現上還可以細細品味,這樣的方式在一些源碼中也有所體現。
  • 在以上的實現中我們是將配置模擬存放到內存中,如果關機了會導致配置信息丟失,因為在一些真實的場景里還是需要存放到數據庫中。那麼此種存放到內存中進行回復的場景也不是沒有,比如;Photoshop、運營人員操作ERP配置活動,那麼也就是即時性的一般不需要存放到庫中進行恢復。另外如果是使用內存方式存放備忘錄,需要考慮存儲問題,避免造成內存大量消耗。
  • 設計模式的學習都是為了更好的寫出可擴展、可管理、易維護的代碼,而這個學習的過程需要自己不斷的嘗試實際操作,理論的知識與實際結合還有很長一段距離。切記多多上手!

七、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式「多種類型商品不同接口,統一發獎服務搭建場景」
  • 2. 重學 Java 設計模式:實戰原型模式「上機考試多套試,每人題目和答案亂序排列場景」
  • 3. 重學 Java 設計模式:實戰橋接模式「多支付渠道(微信、支付寶)與多支付模式(刷臉、指紋)場景」
  • 4. 重學 Java 設計模式:實戰組合模式「營銷差異化人群發券,決策樹引擎搭建場景」
  • 5. 重學 Java 設計模式:實戰外觀模式「基於SpringBoot開發門面模式中間件,統一控制接口白名單場景」
  • 6. 重學 Java 設計模式:實戰享元模式「基於Redis秒殺,提供活動與庫存信息查詢場景」
  • 7. 重學 Java 設計模式:實戰備忘錄模式「模擬互聯網系統上線過程中,配置文件回滾場景」

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

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